在SpringMVC Controller中注入Request成员域

主题

  在工作中遇到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     }

当代理对象的方法被调用的时候,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     }

其中有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     }

其中有一步

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     }

 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     }

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     }

 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     }

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");

上面是一些关键方法

所以最终其实是从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     }

不管你是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     }

其中调用了

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     }

就是在这里设置到RequestContextHolder的threadlocal中去的...

小结

1.在controller中注入的request是jdk动态代理对象,ObjectFactoryDelegatingInvocationHandler的实例.当我们调用成员域request的方法的时候其实是调用了objectFactory的getObject()对象的相关方法.这里的objectFactory是RequestObjectFactory.

2.RequestObjectFactory的getObject其实是从RequestContextHolder的threadlocal中去取值的.

3.请求刚进入springmvc的dispatcherServlet的时候会把request相关对象设置到RequestContextHolder的threadlocal中去.

时间: 2024-11-08 22:53:00

在SpringMVC Controller中注入Request成员域的相关文章

SpringMVC - controller中获取session

平时使用springMVC 在方法中访问session中经常很自然地调用Servlet API. 用起来非常直观方便,一直没有多考虑什么. 比如这样: @RequestMapping(value = "/logout") public String logout(HttpSession session) { session.removeAttribute("user"); return "/login"; } 但毕竟这样对Servlet API产

spring mvc controller中获取request head内容

spring mvc controller中获取request head内容: @RequestMapping("/{mlid}/{ptn}/{name}") public String print(@PathVariable Integer mlid, @PathVariable String ptn, @PathVariable String name, HttpSession session, Model model, @RequestHeader String referer,

在springMVC的controller中获取request,response对象的一个方法

使用springMVC的时候,有些时候会需要获取请求或者响应对象,例如在身份验证的时候,需要获取请求头中的token,在做登录系统的时候需要使用response对象向客户端添加cookie,一个有效的做法是在controller的方法中添加对应参数如下所示: @RestController public class Test2Contrller { @RequestMapping("/test") public void test(HttpServletRequest req, Htt

Controller中request的疑问

1. springmvc 的Controller 中 注入request 线程安全吗? 原理是什么? 2. springmvc 的Controller 中如下代码线程安全吗? @Controller public class AController{ protected HttpServletRequest request; protected HttpServletResponse response; @ModelAttribute public void bindRequestObject(

spring MVC 中获取request

spring MVC中如何获取request 呢? 有如下方式: 方式一:在action中注入request 直接在action的参数中增加HttpServletRequest request 例如 /*** * 返回json * @param id * @param roleLevel * @param model * @param request * @param targetView * @return * @throws SecurityException * @throws NoSuc

SpringMVC项目中中文字符乱码问题及解决办法总结(非专业最优解决办法) -- ajax传值乱码; request.getParameter()乱码;

情况一: ajax中传值时是乱码(后台可以获取到中文字符,但用@ResponseBody返回时前台为乱码) 情况二: Controller 中 request.getParameter()获取到的是乱码 @RequestMapping(params = "method=submit") public String submit(HttpServletRequest request, ModelMap modelMap) throws Exception{ String uname =

springmvc框架的项目,如何在controller中使用dao访问数据库

在springmvc框架中,controller和其他的bean是被分别扫描的,原因是如果不这样配置,controller就会被扫描两次,从而导致事物等问题. 通常情况下,controller是不允许直接使用dao的,一个合理的设计就是在sevice中去调用dao. 但是如果有临时的需求需要在controller中访问数据库,那怎么办呢? 有两种方法. 方法1:在web.xml中配置,使spring对事物的控制设计请求的整个生命周期. <!--配置Spring的OpenSessionInView

springmvc中获取request对象,加载biz(service)的方法

获取request对象: 首先配置web.xml文件--> [html] view plaincopy <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> 然后在程序中获取: 代码: [java] view plaincopy HttpServletRequest 

C语言 结构体中的成员域偏移量

//C语言中结构体中的成员域偏移量 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> typedef struct _student{ char name[30];//32 int num; }Student; void main(){ Student *p = NULL; printf("%x\n", p);//打印 0 p