【轉】在SpringMVC Controller中注入Request成員域
原文連結:https://www.cnblogs.com/abcwt112/p/7777258.html
原文作者:abcwt112
主題
在工作中遇到1個問題....我們定義了一個Controller基類,所有Springmvc自定義的controller都繼承它....在它內部定義一個@Autowired HttpServletRequest request;可不可以? 能不能從這個物件裡取requestParamters和attributes? 多執行緒之間會不會影響?
思考
初次思考,我想這應該是不行的.為什麼呢?
注入bean是在spring容器啟動的時候...request的實現類是在tomcat裡(我使用的servlet容器是tomcat)....我又沒在spring的容器裡配置這個bean.注入應該是失敗的...
退一步說,就算是成功了....那注入的也就是1個物件而已.每次servlet接受到請求都會重新生成1個request...這明顯和之前啟動的那個物件不同吧....怎麼想都不可能成功...
如果確實是這樣的....那就沒有這篇文章了....後來實踐了一下..發現這個注入是可以的.使用起來取資料也沒任何問題....
其實我那個時候debug看了一下,基本就知道為什麼可以取到資料了..但是我並不知道原理和Spring(Springmvc)的處理流程...所以現在研究了一下並記錄下來...
原理
首先給大家看一下在方法中注入request作為引數和在成員域中注入request的 注入的request物件之間的區別....
成員域注入的時候注入的是1個代理物件.是 AutowireUtils.ObjectFactoryDelegatingInvocationHandler的例項.
方法注入的就是一般tomcat原生的requestFacade物件.
所以這是不同的...
/** * Reflective InvocationHandler for lazy access to the current target object. */ @SuppressWarnings("serial") private staticclass 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 { String methodName = method.getName(); if (methodName.equals("equals")) { // Only consider equal when proxies are identical. return (proxy == args[0]); } else if (methodName.equals("hashCode")) { // Use hashCode of proxy. return System.identityHashCode(proxy); } else if (methodName.equals("toString")) { return this.objectFactory.toString(); } try { return method.invoke(this.objectFactory.getObject(), args); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } } }
當代理物件(就是成員域request)的大部分方法被呼叫的時候,ObjectFactoryDelegatingInvocationHandler會使用objectFactory獲取物件(原生request),再呼叫物件上的方法.
然後我們來看下XmlWebApplicationContext初始化到請求到進入controller裡幾個對注入request成員域有影響的步驟.
refresh方法和postProcessBeanFactory方法
ApplicationContext的抽象實現類AbstractApplicationContext(基本是所有ac的父類)裡定義了ac的refresh方法(包含了使用BeanFactory注入bean)的流程..
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. // 記錄開始wac開始初始化的時間,設定啟用標記,servlet的相關param設定到env(之前做過1次),校驗env中必須的props prepareRefresh(); // Tell the subclass to refresh the internal bean factory. // 將舊的BF裡的bean刪掉,新建1個BF,設定部分屬性,載入XML配置檔案 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. // 1.設定BF解析bean配置需要用到的一些物件比如env. 2.註冊一些BeanPostProcessor比如ApplicationContextAwareProcessor去設定Aware需要的物件 // 3.忽略一些特定class注入的物件,設定一些特定class注入的物件為指定值 // 4.將一些env中的properties map當做bean註冊到BF中 prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. // 1.設定一個BeanPostProcess為ServletContextAware的實現類注入servlet相關物件 // 2.在BF中增加requetsScope等Scope // 3.把servletContext,Config,ServletInitParams,ServletAttribute當做Bean註冊到BF中 postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. // 初始化並呼叫BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. // 註冊BeanPostProcessors並註冊到BF中去 registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex); // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
其中有1個模板方法
postProcessBeanFactory(beanFactory);
這個方法允許AbstractApplicationContext的子類覆蓋它並實現對BF的定製(這個時候bean的defination路徑已經指定了,但是bean還沒載入).
AbstractRefreshableWebApplicationContext覆蓋了這個方法
/** * Register request/session scopes, a {@link ServletContextAwareProcessor}, etc. * 1.設定一個BeanPostProcess為ServletContextAware的實現類注入servlet相關物件 * 2.在BF中增加requetsScope等Scope * 3.把servletContext,Config,ServletInitParams,ServletAttribute當做Bean註冊到BF中 * */ @Override protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { // 設定一個BeanPostProcess為ServletContextAware的實現類注入servlet相關物件 beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig)); beanFactory.ignoreDependencyInterface(ServletContextAware.class); beanFactory.ignoreDependencyInterface(ServletConfigAware.class); // 在BF中增加requetsScope等Scope WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext); // 把servletContext,Config,ServletInitParams,ServletAttribute當做Bean註冊到BF中 WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig); }
其中有一步
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
這裡設定了一些特殊的bean的scope,比如request,session,globalSession,application.(當然這個不是我這篇文章的主題.)
同時設定了一些特殊的autowired bean
beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
ServletRequest的實現類(比如HttpServletRequest)被指定使用RequestObjectFactory注入.
RequestObjectFactory
RequestObjectFactory就是1個ObjectFactory就是前面ObjectFactoryDelegatingInvocationHandler裡的ObjectFactory.所以在成員域request物件上呼叫方法其實就是通過RequestObjectFactory獲取物件再呼叫方法.
/** * Factory that exposes the current request object on demand. */ @SuppressWarnings("serial") private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable { @Override public ServletRequest getObject() { return currentRequestAttributes().getRequest(); } @Override public String toString() { return "Current HttpServletRequest"; } }
/** * 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; }
RequestObjectFactory的getObject方法很簡單,就是呼叫靜態方法
RequestContextHolder.currentRequestAttributes().getRequest()
RequestContextHolder
public static RequestAttributes currentRequestAttributes() throws IllegalStateException { RequestAttributes attributes = getRequestAttributes(); if (attributes == null) { if (jsfPresent) { attributes = FacesRequestAttributesFactory.getFacesRequestAttributes(); } if (attributes == null) { throw new IllegalStateException("No thread-bound request found: " + "Are you referring to request attributes outside of an actual web request, " + "or processing a request outside of the originally receiving thread? " + "If you are actually operating within a web request and still receive this message, " + "your code is probably running outside of DispatcherServlet/DispatcherPortlet: " + "In this case, use RequestContextListener or RequestContextFilter to expose the current request."); } } return attributes; }
/** * Return the RequestAttributes currently bound to the thread. * @return the RequestAttributes currently bound to the thread, * or {@code null} if none bound */ public static RequestAttributes getRequestAttributes() { RequestAttributes attributes = requestAttributesHolder.get(); if (attributes == null) { attributes = inheritableRequestAttributesHolder.get(); } return attributes; }
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<RequestAttributes>("Request attributes"); private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal<RequestAttributes>("Request context");
上面是一些關鍵方法
所以最終其實request是從threadlocal中取...
FrameworkServlet
那麼request是什麼時候設定到threadlocal中去的呢?
是在Springmvc的dispatcherServlet的父類FrameworkServlet裡操作的.
@Override protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } @Override protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); }
不管你是doGet還是doPost還是doXXX方法都是委託processRequest方法去做的.
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); initContextHolders(request, localeContext, requestAttributes); try { doService(request, response); } catch (ServletException ex) { failureCause = ex; throw ex; } catch (IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } if (logger.isDebugEnabled()) { if (failureCause != null) { this.logger.debug("Could not complete request", failureCause); } else { if (asyncManager.isConcurrentHandlingStarted()) { logger.debug("Leaving response open for concurrent processing"); } else { this.logger.debug("Successfully completed request"); } } } publishRequestHandledEvent(request, response, startTime, failureCause); } }
其中呼叫了
initContextHolders(request, localeContext, requestAttributes);
private void initContextHolders( HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) { if (localeContext != null) { LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable); } if (requestAttributes != null) { RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable); } if (logger.isTraceEnabled()) { logger.trace("Bound request context to thread: " + request); } }
就是在這裡設定到RequestContextHolder的threadlocal中去的...
小結
1.在controller中注入的request是jdk動態代理物件,ObjectFactoryDelegatingInvocationHandler的例項.當我們呼叫成員域request的方法的時候其實是呼叫了objectFactory的getObject()物件的相關方法.這裡的objectFactory是RequestObjectFactory.
2.RequestObjectFactory的getObject其實是從RequestContextHolder的threadlocal中去取值的.
3.請求剛進入springmvc的dispatcherServlet的時候會把request相關物件設定到RequestContextHolder的threadlocal中去.