Spring?web開發(fā)教程之Request獲取3種方式
前言
在開發(fā) Java Web 項目中,我們經(jīng)常使用 HttpServletRequest 獲取請求參數(shù)、請求頭等信息。在Spring項目,我們通常會使用 Spring 提供的注解獲取參數(shù),如 @RequestParam、@RequestHeader。
不過在某些場景下,我們可能需要從 HttpServletRequest 對象中取得更多的能力,如獲取請求 IP,獲取請求域名等。這篇我們來學(xué)習(xí)如何在 Spring MVC 環(huán)境下獲取 HttpServletRequest,以及它們的實現(xiàn)方式,做到知其所以然。
Controller 方法參數(shù)
使用注解后的 Spring MVC controller 方法可以作為 handler 處理請求,如果想獲取 request 對象,只需要在方法中添加 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ù)實現(xiàn)原理
通過上面的代碼我們很容易就實現(xiàn)了,那spring是怎么幫我們搞定的呢?
- 在spring mvc中,所有瀏覽器發(fā)起的請求都會先交給DispatcherServlet 處理。
- DispatcherServlet 根據(jù)用戶或默認(rèn)的配置使用 HandlerMapping 查找可處理請求的處理器。
- DispatcherServlet 拿到 HandlerMapping 返回的處理器鏈 HandlerExecutionChain。整個處理器鏈包含攔截器和處理。
- DispatcherServlet 將處理器適配為 HandlerAdapter。
- DispatcherServlet 使用攔截器進(jìn)行請求前置處理。
- DispatcherServlet 使用處理器進(jìn)行請求處理。
- DispatcherServlet 使用攔截器進(jìn)行請求后置處理。
- DispatcherServlet 從攔截器或處理器中提取到模型及視圖 ModelAndView。
- DispatcherServlet 使用視圖解析器 ViewResolver 解析視圖出視圖 View。
- DispatcherServlet 渲染視圖,響應(yīng)請求返回給瀏覽器。
在上面第6步【DispatcherServlet 使用處理器進(jìn)行請求處理】時,在調(diào)用我們自己的controller方法之前,Spring通過
HandlerMethodArgumentResolver向我們的controller方法注入對應(yīng)的參數(shù)。
靜態(tài)方法
除了通過 controller 方法參數(shù)獲取 HttpServletRequest 對象,Spring 還允許通過其提供的工具類的靜態(tài)方法來獲取 HttpServletRequest。示例如下。
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
靜態(tài)方法實現(xiàn)原理
上面的示例中,RequestContextHolder 表示一個請求上下文的持有者,內(nèi)部將每個請求上下文信息存儲到 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 處理請求前會將 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 也很簡單,想想這里會有問題嗎?.......
Controller 不是一個單例 bean 對象嗎?在一個 Spring 容器內(nèi)只有一個實例,而每次請求都對應(yīng)一個 request 對象,Spring 是怎樣做到使用一個 request 表示多個請求的?
經(jīng)過仔細(xì)分析,我們可以發(fā)現(xiàn) Spring 注入 bean 時使用了底層的
DefaultListableBeanFactory 獲取 bean 實例,相關(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 時會先從 resolvableDependencies 中查找,找到后調(diào)用 AutowireUtils.resolveAutowiringValue方法再次解析。
resolvableDependencies中對象是 Spring 中特殊的存在,不屬于 Spring 管理的 bean,需要手動注冊到
DefaultListableBeanFactory。
我們繼續(xù)跟蹤源碼。
abstract class AutowireUtils { public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) { if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) { // ObjectFactory 類型值和所需類型不匹配,創(chuàng)建代理對象 ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue; if (autowiringValue instanceof Serializable && requiredType.isInterface()) { // 創(chuàng)建代理對象,可用于處理 HttpServletRequest 注入等問題 autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(), new Class<?>[]{requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory)); } else { return factory.getObject(); } } return autowiringValue; } }
當(dāng)resolvableDependencies中對象是ObjectFactory 類型,并且與所需的類型不匹配,Spring 使用 ObjectFactory 創(chuàng)建了一個 JDK 代理對象:
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(); } } }
代理的實現(xiàn)簡單,每當(dāng)所需類型的方法調(diào)用時,就調(diào)用 ObjectFactory 中獲取的實例對象的對應(yīng)方法。
那怎么與HttpServletRequest關(guān)聯(lián)啟來呢?
Spring 在啟動時會注冊 Web 環(huán)境相關(guān)的依賴對象
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 類型對應(yīng) ObjectFactory 注冊 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 類型,那再看看它的實現(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場景中,獲取request的方法,get到了嗎?
總結(jié)
到此這篇關(guān)于Spring web開發(fā)教程之Request獲取3種方式的文章就介紹到這了,更多相關(guān)Spring web獲取Request內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java web實現(xiàn)賬號單一登錄,防止同一賬號重復(fù)登錄(踢人效果)
這篇文章主要介紹了Java web實現(xiàn)賬號單一登錄,防止同一賬號重復(fù)登錄,有點類似于qq登錄踢人效果,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-10-10關(guān)于Jedis的用法以及Jedis使用Redis事務(wù)
這篇文章主要介紹了關(guān)于Jedis的用法以及Jedis使用Redis事務(wù)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03Java數(shù)據(jù)結(jié)構(gòu)之線性表
線性表是其組成元素間具有線性關(guān)系的一種數(shù)據(jù)結(jié)構(gòu),對線性表的基本操作主要有,獲取元素,設(shè)置元素值,遍歷,插入,刪除,查找,替換,排序等。而線性表可以采用順序儲存結(jié)構(gòu)和鏈?zhǔn)絻Υ娼Y(jié)構(gòu),本節(jié)主要講解順序表、單鏈表以及雙鏈表的各種基本操作。2017-03-03Springboot搭建JVM監(jiān)控(Springboot + Prometheus +&n
在應(yīng)用開發(fā)時,監(jiān)控報警必不可少,本文主要介紹了Springboot搭建JVM監(jiān)控(Springboot + Prometheus + Grafana),具有一定的參考價值,感興趣的可以了解一下2024-05-05Java中HashMap和TreeMap的區(qū)別深入理解
首先介紹一下什么是Map。在數(shù)組中我們是通過數(shù)組下標(biāo)來對其內(nèi)容索引的,而在Map中我們通過對象來對對象進(jìn)行索引,用來索引的對象叫做key,其對應(yīng)的對象叫做value2012-12-12