Spring?web開發(fā)教程之Request獲取3種方式
前言
在開發(fā) Java Web 項(xiàng)目中,我們經(jīng)常使用 HttpServletRequest 獲取請(qǐng)求參數(shù)、請(qǐng)求頭等信息。在Spring項(xiàng)目,我們通常會(huì)使用 Spring 提供的注解獲取參數(shù),如 @RequestParam、@RequestHeader。
不過在某些場(chǎng)景下,我們可能需要從 HttpServletRequest 對(duì)象中取得更多的能力,如獲取請(qǐng)求 IP,獲取請(qǐng)求域名等。這篇我們來學(xué)習(xí)如何在 Spring MVC 環(huán)境下獲取 HttpServletRequest,以及它們的實(shí)現(xiàn)方式,做到知其所以然。
Controller 方法參數(shù)
使用注解后的 Spring MVC controller 方法可以作為 handler 處理請(qǐng)求,如果想獲取 request 對(duì)象,只需要在方法中添加 ServletRequest 或 HttpServletRequest 類型參數(shù)即可。代碼如下
@RestController
public class UserController {
@GetMapping("/getUser")
public String getUser(HttpServletRequest request) {
return "request ip is : " + request.getRemoteHost();
}
}擴(kuò)展:如何要獲取reponse,同例只要在方法中增加 ServletResponse 或 HttpServletResponse 類型參數(shù)即可。
Controller 方法參數(shù)實(shí)現(xiàn)原理
通過上面的代碼我們很容易就實(shí)現(xiàn)了,那spring是怎么幫我們搞定的呢?

- 在spring mvc中,所有瀏覽器發(fā)起的請(qǐng)求都會(huì)先交給DispatcherServlet 處理。
- DispatcherServlet 根據(jù)用戶或默認(rèn)的配置使用 HandlerMapping 查找可處理請(qǐng)求的處理器。
- DispatcherServlet 拿到 HandlerMapping 返回的處理器鏈 HandlerExecutionChain。整個(gè)處理器鏈包含攔截器和處理。
- DispatcherServlet 將處理器適配為 HandlerAdapter。
- DispatcherServlet 使用攔截器進(jìn)行請(qǐng)求前置處理。
- DispatcherServlet 使用處理器進(jìn)行請(qǐng)求處理。
- DispatcherServlet 使用攔截器進(jìn)行請(qǐng)求后置處理。
- DispatcherServlet 從攔截器或處理器中提取到模型及視圖 ModelAndView。
- DispatcherServlet 使用視圖解析器 ViewResolver 解析視圖出視圖 View。
- DispatcherServlet 渲染視圖,響應(yīng)請(qǐng)求返回給瀏覽器。
在上面第6步【DispatcherServlet 使用處理器進(jìn)行請(qǐng)求處理】時(shí),在調(diào)用我們自己的controller方法之前,Spring通過
HandlerMethodArgumentResolver向我們的controller方法注入對(duì)應(yīng)的參數(shù)。

靜態(tài)方法
除了通過 controller 方法參數(shù)獲取 HttpServletRequest 對(duì)象,Spring 還允許通過其提供的工具類的靜態(tài)方法來獲取 HttpServletRequest。示例如下。
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
靜態(tài)方法實(shí)現(xiàn)原理
上面的示例中,RequestContextHolder 表示一個(gè)請(qǐng)求上下文的持有者,內(nèi)部將每個(gè)請(qǐng)求上下文信息存儲(chǔ)到 ThreadLocal 中。
public abstract class RequestContextHolder {
/**
* 線程上下文 RequestAttributes
*/
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
/**
* 支持繼承的線程上下文 RequestAttributes
*/
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
}DispatcherServlet 處理請(qǐng)求前會(huì)將 request 存至 ServletRequestAttributes,然后放到 RequestContextHolder 中,具體可見DispatcherServlet的父類
FrameworkServlet.processRequest()。
直接注入
還可以將 HttpServletRequest 當(dāng)做普通的 bean 注入。代碼如下
@RestController
public class UserController {
@Autowired
private HttpServletRequest request;
@GetMapping("/getIP")
public String getIP() {
return "request ip is : " + request.getRemoteHost();
}
}直接注入分析
通過 @Autowired 的方式引入 request 也很簡單,想想這里會(huì)有問題嗎?.......
Controller 不是一個(gè)單例 bean 對(duì)象嗎?在一個(gè) Spring 容器內(nèi)只有一個(gè)實(shí)例,而每次請(qǐng)求都對(duì)應(yīng)一個(gè) request 對(duì)象,Spring 是怎樣做到使用一個(gè) request 表示多個(gè)請(qǐng)求的?
經(jīng)過仔細(xì)分析,我們可以發(fā)現(xiàn) Spring 注入 bean 時(shí)使用了底層的
DefaultListableBeanFactory 獲取 bean 實(shí)例,相關(guān)代碼如下。
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
// 不依賴關(guān)系
private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16);
// 查找候選 bean
protected Map<String, Object> findAutowireCandidates(
@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
//部分代碼省略
Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
Class<?> autowiringType = classObjectEntry.getKey();
if (autowiringType.isAssignableFrom(requiredType)) {
Object autowiringValue = classObjectEntry.getValue();
// 解析 ObjectFactory
autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
if (requiredType.isInstance(autowiringValue)) {
result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
break;
}
}
}
//部分代碼省略
}
}DefaultListableBeanFactory 查找候選 bean 時(shí)會(huì)先從 resolvableDependencies 中查找,找到后調(diào)用 AutowireUtils.resolveAutowiringValue方法再次解析。
resolvableDependencies中對(duì)象是 Spring 中特殊的存在,不屬于 Spring 管理的 bean,需要手動(dòng)注冊(cè)到
DefaultListableBeanFactory。
我們繼續(xù)跟蹤源碼。
abstract class AutowireUtils {
public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
// ObjectFactory 類型值和所需類型不匹配,創(chuàng)建代理對(duì)象
ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
// 創(chuàng)建代理對(duì)象,可用于處理 HttpServletRequest 注入等問題
autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),
new Class<?>[]{requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));
} else {
return factory.getObject();
}
}
return autowiringValue;
}
}當(dāng)resolvableDependencies中對(duì)象是ObjectFactory 類型,并且與所需的類型不匹配,Spring 使用 ObjectFactory 創(chuàng)建了一個(gè) JDK 代理對(duì)象:
private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable {
private final ObjectFactory<?> objectFactory;
public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) {
this.objectFactory = objectFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return method.invoke(this.objectFactory.getObject(), args);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}代理的實(shí)現(xiàn)簡單,每當(dāng)所需類型的方法調(diào)用時(shí),就調(diào)用 ObjectFactory 中獲取的實(shí)例對(duì)象的對(duì)應(yīng)方法。
那怎么與HttpServletRequest關(guān)聯(lián)啟來呢?
Spring 在啟動(dòng)時(shí)會(huì)注冊(cè) Web 環(huán)境相關(guān)的依賴對(duì)象
public abstract class WebApplicationContextUtils {
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
@Nullable ServletContext sc) {
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope());
if (sc != null) {
ServletContextScope appScope = new ServletContextScope(sc);
beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
// Register as ServletContext attribute, for ContextCleanupListener to detect it.
sc.setAttribute(ServletContextScope.class.getName(), appScope);
}
// ServletRequest 類型對(duì)應(yīng) ObjectFactory 注冊(cè)
beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
if (jsfPresent) {
FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
}
}
}可以看到:Spring 為 ServletRequest 注入的是 RequestObjectFactory 類型,那再看看它的實(shí)現(xiàn):
public abstract class WebApplicationContextUtils {
private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
@Override
public ServletRequest getObject() {
return currentRequestAttributes().getRequest();
}
/**
* Return the current RequestAttributes instance as ServletRequestAttributes.
* @see RequestContextHolder#currentRequestAttributes()
*/
private static ServletRequestAttributes currentRequestAttributes() {
RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
if (!(requestAttr instanceof ServletRequestAttributes)) {
throw new IllegalStateException("Current request is not a servlet request");
}
return (ServletRequestAttributes) requestAttr;
}
@Override
public String toString() {
return "Current HttpServletRequest";
}
}
}可以看到,和前面介紹的【靜態(tài)方法】思路一樣。
以上就是3種在spring場(chǎng)景中,獲取request的方法,get到了嗎?
總結(jié)
到此這篇關(guān)于Spring web開發(fā)教程之Request獲取3種方式的文章就介紹到這了,更多相關(guān)Spring web獲取Request內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解析SpringCloud簡介與微服務(wù)架構(gòu)
這篇文章主要介紹了SpringCloud簡介與微服務(wù)架構(gòu),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
JavaWeb使用mvc模式實(shí)現(xiàn)登錄功能
本文主要介紹了JavaWeb使用mvc模式實(shí)現(xiàn)登錄功能,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
SSH框架網(wǎng)上商城項(xiàng)目第6戰(zhàn)之基于DataGrid的數(shù)據(jù)顯示
SSH框架網(wǎng)上商城項(xiàng)目第6戰(zhàn)之基于DataGrid的數(shù)據(jù)顯示,提供了豐富的選擇、排序、分組和編輯數(shù)據(jù)的功能支持,感興趣的小伙伴們可以參考一下2016-05-05
SpringBoot集成WebService(wsdl)實(shí)踐
文章介紹了Spring Boot項(xiàng)目中通過緩存IWebService接口實(shí)現(xiàn)類的泛型入?yún)㈩愋?減少反射調(diào)用提升性能的實(shí)現(xiàn)方案,包含依賴配置、工具類封裝、接口定義及soapUI調(diào)用參數(shù)說明2025-09-09
通過MyBatis讀取數(shù)據(jù)庫數(shù)據(jù)并提供rest接口訪問
這篇文章主要介紹了通過MyBatis讀取數(shù)據(jù)庫數(shù)據(jù)并提供rest接口訪問 的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08
Java中InputStream重復(fù)使用問題的幾種解決方案
在Java開發(fā)中,InputStream是用于讀取字節(jié)流的類,在許多場(chǎng)景下,我們可能需要重復(fù)讀取InputStream中的數(shù)據(jù),這篇文章主要介紹了Java中InputStream重復(fù)使用問題的幾種解決方案,需要的朋友可以參考下2025-08-08
java實(shí)現(xiàn)多線程文件的斷點(diǎn)續(xù)傳
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)多線程文件的斷點(diǎn)續(xù)傳,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
Spring Boot集成Mybatis中如何顯示日志的實(shí)現(xiàn)
這篇文章主要介紹了Spring Boot集成Mybatis中如何顯示日志的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Java多線程ThreadForge的實(shí)現(xiàn)
本文主要介紹了Java多線程ThreadForge的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2026-05-05

