1. 程式人生 > >在SpringMVC Controller中註入Request成員域

在SpringMVC Controller中註入Request成員域

rec binding stp null 實現類 exceptio locale start 裏的

主題

  在工作中遇到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對象.

所以這是不同的...

技術分享
 1     /**
 2      * Reflective InvocationHandler for lazy access to the current target object.
 3      */
 4     @SuppressWarnings("serial")
 5     private static class
ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable { 6 7 private final ObjectFactory<?> objectFactory; 8 9 public ObjectFactoryDelegatingInvocationHandler(ObjectFactory<?> objectFactory) { 10 this.objectFactory = objectFactory; 11 } 12 13 @Override 14 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 15 String methodName = method.getName(); 16 if (methodName.equals("equals")) { 17 // Only consider equal when proxies are identical. 18 return (proxy == args[0]); 19 } 20 else if (methodName.equals("hashCode")) { 21 // Use hashCode of proxy. 22 return System.identityHashCode(proxy); 23 } 24 else if (methodName.equals("toString")) { 25 return this.objectFactory.toString(); 26 } 27 try { 28 return method.invoke(this.objectFactory.getObject(), args); 29 } 30 catch (InvocationTargetException ex) { 31 throw ex.getTargetException(); 32 } 33 } 34 }
View Code

當代理對象的方法被調用的時候,ObjectFactoryDelegatingInvocationHandler會使用objectFactory獲取對象,再調用對象上的方法.

然後我們來看下XmlWebApplicationContext初始化到請求到進入controller裏幾個對註入request成員域有影響的步驟.

refresh方法和postProcessBeanFactory方法

ApplicationContext的抽象實現類AbstractApplicationContext(基本是所有ac的父類)裏定義了ac的refresh(包含了使用BeanFactory註入bean)的流程..

技術分享
 1 @Override
 2     public void refresh() throws BeansException, IllegalStateException {
 3         synchronized (this.startupShutdownMonitor) {
 4             // Prepare this context for refreshing.
 5             // 記錄開始wac開始初始化的時間,設置激活標記,servlet的相關param設置到env(之前做過1次),校驗env中必須的props
 6             prepareRefresh();
 7 
 8             // Tell the subclass to refresh the internal bean factory.
 9             // 將舊的BF裏的bean刪掉,新建1個BF,設置部分屬性,加載XML配置文件
10             ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
11 
12             // Prepare the bean factory for use in this context.
13             // 1.設置BF解析bean配置需要用到的一些對象比如env. 2.註冊一些BeanPostProcessor比如ApplicationContextAwareProcessor去設置Aware需要的對象
14             // 3.忽略一些特定class註入的對象,設置一些特定class註入的對象為指定值
15             // 4.將一些env中的properties map當做bean註冊到BF中
16             prepareBeanFactory(beanFactory);
17 
18             try {
19                 // Allows post-processing of the bean factory in context subclasses.
20                 // 1.設置一個BeanPostProcess為ServletContextAware的實現類註入servlet相關對象
21                 // 2.在BF中增加requetsScope等Scope
22                 // 3.把servletContext,Config,ServletInitParams,ServletAttribute當做Bean註冊到BF中
23                 postProcessBeanFactory(beanFactory);
24 
25                 // Invoke factory processors registered as beans in the context.
26                 // 初始化並調用BeanFactoryPostProcessor
27                 invokeBeanFactoryPostProcessors(beanFactory);
28 
29                 // Register bean processors that intercept bean creation.
30                 // 註冊BeanPostProcessors並註冊到BF中去
31                 registerBeanPostProcessors(beanFactory);
32 
33                 // Initialize message source for this context.
34                 initMessageSource();
35 
36                 // Initialize event multicaster for this context.
37                 initApplicationEventMulticaster();
38 
39                 // Initialize other special beans in specific context subclasses.
40                 onRefresh();
41 
42                 // Check for listener beans and register them.
43                 registerListeners();
44 
45                 // Instantiate all remaining (non-lazy-init) singletons.
46                 finishBeanFactoryInitialization(beanFactory);
47 
48                 // Last step: publish corresponding event.
49                 finishRefresh();
50             } catch (BeansException ex) {
51                 logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);
52 
53                 // Destroy already created singletons to avoid dangling resources.
54                 destroyBeans();
55 
56                 // Reset ‘active‘ flag.
57                 cancelRefresh(ex);
58 
59                 // Propagate exception to caller.
60                 throw ex;
61             }
62         }
63     }
View Code

其中有1個模板方法

postProcessBeanFactory(beanFactory);

這個允許AbstractApplicationContext的子類覆蓋它實現對BF的定制(這個時候bean的defination路徑已經指定了,但是bean還沒加載).

AbstractRefreshableWebApplicationContext覆蓋了這個方法

技術分享
 1     /**
 2      * Register request/session scopes, a {@link ServletContextAwareProcessor}, etc.
 3      * 1.設置一個BeanPostProcess為ServletContextAware的實現類註入servlet相關對象
 4      * 2.在BF中增加requetsScope等Scope
 5      * 3.把servletContext,Config,ServletInitParams,ServletAttribute當做Bean註冊到BF中
 6      *
 7      */
 8     @Override
 9     protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
10         // 設置一個BeanPostProcess為ServletContextAware的實現類註入servlet相關對象
11         beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
12         beanFactory.ignoreDependencyInterface(ServletContextAware.class);
13         beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
14 
15         // 在BF中增加requetsScope等Scope
16         WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
17         // 把servletContext,Config,ServletInitParams,ServletAttribute當做Bean註冊到BF中
18         WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
19     }
View Code

其中有一步

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獲取對象再調用方法.

技術分享
 1     /**
 2      * Factory that exposes the current request object on demand.
 3      */
 4     @SuppressWarnings("serial")
 5     private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
 6 
 7         @Override
 8         public ServletRequest getObject() {
 9             return currentRequestAttributes().getRequest();
10         }
11 
12         @Override
13         public String toString() {
14             return "Current HttpServletRequest";
15         }
16     }
View Code

技術分享
 1     /**
 2      * Return the current RequestAttributes instance as ServletRequestAttributes.
 3      *
 4      * @see RequestContextHolder#currentRequestAttributes()
 5      */
 6     private static ServletRequestAttributes currentRequestAttributes() {
 7         RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
 8         if (!(requestAttr instanceof ServletRequestAttributes)) {
 9             throw new IllegalStateException("Current request is not a servlet request");
10         }
11         return (ServletRequestAttributes) requestAttr;
12     }
View Code

RequestObjectFactory的getObject方法很簡單,就是調用靜態方法

RequestContextHolder.currentRequestAttributes().getRequest()

RequestContextHolder

技術分享
 1     public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
 2         RequestAttributes attributes = getRequestAttributes();
 3         if (attributes == null) {
 4             if (jsfPresent) {
 5                 attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
 6             }
 7             if (attributes == null) {
 8                 throw new IllegalStateException("No thread-bound request found: " +
 9                         "Are you referring to request attributes outside of an actual web request, " +
10                         "or processing a request outside of the originally receiving thread? " +
11                         "If you are actually operating within a web request and still receive this message, " +
12                         "your code is probably running outside of DispatcherServlet/DispatcherPortlet: " +
13                         "In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
14             }
15         }
16         return attributes;
17     }
View Code 技術分享
 1     /**
 2      * Return the RequestAttributes currently bound to the thread.
 3      * @return the RequestAttributes currently bound to the thread,
 4      * or {@code null} if none bound
 5      */
 6     public static RequestAttributes getRequestAttributes() {
 7         RequestAttributes attributes = requestAttributesHolder.get();
 8         if (attributes == null) {
 9             attributes = inheritableRequestAttributesHolder.get();
10         }
11         return attributes;
12     }
View Code 技術分享
1     private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
2             new NamedThreadLocal<RequestAttributes>("Request attributes");
3 
4     private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
5             new NamedInheritableThreadLocal<RequestAttributes>("Request context");
View Code

上面是一些關鍵方法

所以最終其實是從request是從threadlocal中取...

FrameworkServlet

那麽request是什麽時候設置到threadlocal中去的呢?

是在Springmvc的dispatcherServlet的父類FrameworkServlet裏操作的.

技術分享
 1     /**
 2      * Delegate GET requests to processRequest/doService.
 3      * <p>Will also be invoked by HttpServlet‘s default implementation of {@code doHead},
 4      * with a {@code NoBodyResponse} that just captures the content length.
 5      * @see #doService
 6      * @see #doHead
 7      */
 8     @Override
 9     protected final void doGet(HttpServletRequest request, HttpServletResponse response)
10             throws ServletException, IOException {
11 
12         processRequest(request, response);
13     }
14 
15     /**
16      * Delegate POST requests to {@link #processRequest}.
17      * @see #doService
18      */
19     @Override
20     protected final void doPost(HttpServletRequest request, HttpServletResponse response)
21             throws ServletException, IOException {
22 
23         processRequest(request, response);
24     }
View Code

不管你是doGet還是doPost還是doXXX方法都是委托processRequest方法去做的.

技術分享
 1 /**
 2      * Process this request, publishing an event regardless of the outcome.
 3      * <p>The actual event handling is performed by the abstract
 4      * {@link #doService} template method.
 5      */
 6     protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
 7             throws ServletException, IOException {
 8 
 9         long startTime = System.currentTimeMillis();
10         Throwable failureCause = null;
11 
12         LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
13         LocaleContext localeContext = buildLocaleContext(request);
14 
15         RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
16         ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
17 
18         WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
19         asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
20 
21         initContextHolders(request, localeContext, requestAttributes);
22 
23         try {
24             doService(request, response);
25         }
26         catch (ServletException ex) {
27             failureCause = ex;
28             throw ex;
29         }
30         catch (IOException ex) {
31             failureCause = ex;
32             throw ex;
33         }
34         catch (Throwable ex) {
35             failureCause = ex;
36             throw new NestedServletException("Request processing failed", ex);
37         }
38 
39         finally {
40             resetContextHolders(request, previousLocaleContext, previousAttributes);
41             if (requestAttributes != null) {
42                 requestAttributes.requestCompleted();
43             }
44 
45             if (logger.isDebugEnabled()) {
46                 if (failureCause != null) {
47                     this.logger.debug("Could not complete request", failureCause);
48                 }
49                 else {
50                     if (asyncManager.isConcurrentHandlingStarted()) {
51                         logger.debug("Leaving response open for concurrent processing");
52                     }
53                     else {
54                         this.logger.debug("Successfully completed request");
55                     }
56                 }
57             }
58 
59             publishRequestHandledEvent(request, response, startTime, failureCause);
60         }
61     }
View Code

其中調用了

initContextHolders(request, localeContext, requestAttributes);

技術分享
 1     private void initContextHolders(
 2             HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) {
 3 
 4         if (localeContext != null) {
 5             LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
 6         }
 7         if (requestAttributes != null) {
 8             RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
 9         }
10         if (logger.isTraceEnabled()) {
11             logger.trace("Bound request context to thread: " + request);
12         }
13     }
View Code

就是在這裏設置到RequestContextHolder的threadlocal中去的...

小結

1.在controller中註入的request是jdk動態代理對象,ObjectFactoryDelegatingInvocationHandler的實例.當我們調用成員域request的方法的時候其實是調用了objectFactory的getObject()對象的相關方法.這裏的objectFactory是RequestObjectFactory.

2.RequestObjectFactory的getObject其實是從RequestContextHolder的threadlocal中去取值的.

3.請求剛進入springmvc的dispatcherServlet的時候會把request相關對象設置到RequestContextHolder的threadlocal中去.

在SpringMVC Controller中註入Request成員域