Spring?web開(kāi)發(fā)教程之Request獲取3種方式
前言
在開(kāi)發(fā) Java Web 項(xiàng)目中,我們經(jīng)常使用 HttpServletRequest 獲取請(qǐng)求參數(shù)、請(qǐng)求頭等信息。在Spring項(xiàng)目,我們通常會(huì)使用 Spring 提供的注解獲取參數(shù),如 @RequestParam、@RequestHeader。
不過(guò)在某些場(chǎng)景下,我們可能需要從 HttpServletRequest 對(duì)象中取得更多的能力,如獲取請(qǐng)求 IP,獲取請(qǐng)求域名等。這篇我們來(lái)學(xué)習(xí)如何在 Spring MVC 環(huán)境下獲取 HttpServletRequest,以及它們的實(shí)現(xiàn)方式,做到知其所以然。
Controller 方法參數(shù)
使用注解后的 Spring MVC controller 方法可以作為 handler 處理請(qǐng)求,如果想獲取 request 對(duì)象,只需要在方法中添加 ServletRequest 或 HttpServletRequest 類(lèi)型參數(shù)即可。代碼如下
@RestController public class UserController { @GetMapping("/getUser") public String getUser(HttpServletRequest request) { return "request ip is : " + request.getRemoteHost(); } }
擴(kuò)展:如何要獲取reponse,同例只要在方法中增加 ServletResponse 或 HttpServletResponse 類(lèi)型參數(shù)即可。
Controller 方法參數(shù)實(shí)現(xiàn)原理
通過(guò)上面的代碼我們很容易就實(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通過(guò)
HandlerMethodArgumentResolver向我們的controller方法注入對(duì)應(yīng)的參數(shù)。
靜態(tài)方法
除了通過(guò) controller 方法參數(shù)獲取 HttpServletRequest 對(duì)象,Spring 還允許通過(guò)其提供的工具類(lèi)的靜態(tài)方法來(lá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 中,具體可見(jiàn)DispatcherServlet的父類(lèi)
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(); } }
直接注入分析
通過(guò) @Autowired 的方式引入 request 也很簡(jiǎn)單,想想這里會(huì)有問(wèn)題嗎?.......
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)過(guò)仔細(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 類(lèi)型值和所需類(lèi)型不匹配,創(chuàng)建代理對(duì)象 ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue; if (autowiringValue instanceof Serializable && requiredType.isInterface()) { // 創(chuàng)建代理對(duì)象,可用于處理 HttpServletRequest 注入等問(wèn)題 autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(), new Class<?>[]{requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory)); } else { return factory.getObject(); } } return autowiringValue; } }
當(dāng)resolvableDependencies中對(duì)象是ObjectFactory 類(lèi)型,并且與所需的類(lèi)型不匹配,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)簡(jiǎn)單,每當(dāng)所需類(lèi)型的方法調(diào)用時(shí),就調(diào)用 ObjectFactory 中獲取的實(shí)例對(duì)象的對(duì)應(yīng)方法。
那怎么與HttpServletRequest關(guān)聯(lián)啟來(lái)呢?
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 類(lèi)型對(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 類(lèi)型,那再看看它的實(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開(kāi)發(fā)教程之Request獲取3種方式的文章就介紹到這了,更多相關(guān)Spring web獲取Request內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java web實(shí)現(xiàn)賬號(hào)單一登錄,防止同一賬號(hào)重復(fù)登錄(踢人效果)
這篇文章主要介紹了Java web實(shí)現(xiàn)賬號(hào)單一登錄,防止同一賬號(hào)重復(fù)登錄,有點(diǎn)類(lèi)似于qq登錄踢人效果,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10關(guān)于Jedis的用法以及Jedis使用Redis事務(wù)
這篇文章主要介紹了關(guān)于Jedis的用法以及Jedis使用Redis事務(wù)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Java實(shí)現(xiàn)提取QSV文件視頻內(nèi)容
QSV是一種加密的視頻文件格式。是愛(ài)奇藝公司研發(fā)的一種視頻文件格式,這篇文章主要為大家介紹了如何利用Java實(shí)現(xiàn)提取QSV文件視頻內(nèi)容,感興趣的可以了解一下2023-03-03java swing框架實(shí)現(xiàn)貪吃蛇游戲
這篇文章主要為大家詳細(xì)介紹了java swing框架實(shí)現(xiàn)貪吃蛇游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-12-12Java數(shù)據(jù)結(jié)構(gòu)之線性表
線性表是其組成元素間具有線性關(guān)系的一種數(shù)據(jù)結(jié)構(gòu),對(duì)線性表的基本操作主要有,獲取元素,設(shè)置元素值,遍歷,插入,刪除,查找,替換,排序等。而線性表可以采用順序儲(chǔ)存結(jié)構(gòu)和鏈?zhǔn)絻?chǔ)存結(jié)構(gòu),本節(jié)主要講解順序表、單鏈表以及雙鏈表的各種基本操作。2017-03-03Springboot搭建JVM監(jiān)控(Springboot + Prometheus +&n
在應(yīng)用開(kāi)發(fā)時(shí),監(jiān)控報(bào)警必不可少,本文主要介紹了Springboot搭建JVM監(jiān)控(Springboot + Prometheus + Grafana),具有一定的參考價(jià)值,感興趣的可以了解一下2024-05-05Java中HashMap和TreeMap的區(qū)別深入理解
首先介紹一下什么是Map。在數(shù)組中我們是通過(guò)數(shù)組下標(biāo)來(lái)對(duì)其內(nèi)容索引的,而在Map中我們通過(guò)對(duì)象來(lái)對(duì)對(duì)象進(jìn)行索引,用來(lái)索引的對(duì)象叫做key,其對(duì)應(yīng)的對(duì)象叫做value2012-12-12