概述
在Spring
应用中,将一个较大作用域的bean
注入到一个较小作用域的bean
中是很自然的一件事情,因为往较小作用域bean
中注入较大作用域bean
时,较大作用域的bean
已经存在了。但是,如果反过来,将一个较小作用域的bean
往一个较大作用域的bean
中注入时,较小作用域的bean
可能根本不存在,比如Spring MVC
应用中,如果要将一个ServletRequest
注入到一个单例作用域的Web
控制器组件中,则该注入动作会在该单例Web
控制器组件初始化时发生(容器对单例bean
的创建过程决定的),而通常此时容器正在启动,任何用户请求尚未到达,所以注入任何一个ServletRequest
听起来都是不正确的。以此类推,将一个较小作用域的bean
往一个较大作用域的bean
中注入概念上讲是不可行的。
但通过实践,我们明确知道,在一个Spring MVC
应用中,将一个ServletRequest
注入到一个单例作用域的Web
控制器中是可以的。这又是为什么呢 ?这里面,我们有两个方面的问题要解答 :
- 注入的
bean
是什么?为什么ServletRequest
能够注入成功 ? - 注入的
bean
如何工作的?为什么请求过程中访问注入的ServletRequest
能访问到正确的信息?
本文我们将通过分析回答上面的第一个问题。
本文所使用的例子如下 :
@Controller public class HomeController { ServletRequest request; @Autowired public void setRequest(ServletRequest request){ this.request=request; } @RequestMapping("/") @ResponseBody public String home() { return "Hello "+ request.getParameter("name"); } }
工作原理分析
首先,我们知道,如果要想注入ServletRequest
成功,容器中必须存在该种类型的bean
,bean HomeController
的属性ServletRequest request
注入时,尚无任何用户请求达到,那么所注入的到底是什么呢?
在上面例子中行
this.request=request;
设置一个断点,然后运行程序。我们发现,bean HomeController
创建时,request
属性被注入了如下对象 :
request = {$Proxy54@5166} "Current HttpServletRequest"
h = {AutowireUtils$ObjectFactoryDelegatingInvocationHandler@5174}
objectFactory = {WebApplicationContextUtils$RequestObjectFactory@5175} "Current HttpServletRequest"
从这些信息不难看出,request
是一个Java
动态代理对象,该动态代理对象的InvocationHandler
实现类是AutowireUtils$ObjectFactoryDelegatingInvocationHandler
。它所代理的对象是一个对象工厂,实现类型为WebApplicationContextUtils$RequestObjectFactory
。
那bean
WebApplicationContextUtils$RequestObjectFactory
是什么时候注册到容器的呢 ?
我们知道,当前应用是一个Spring MVC
应用,所以它对应的应用上下文是一个 ServletWebServerApplicationContext
。而在它的postProcessBeanFactory
阶段,它调用了WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory())
:
// 工具类 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的对象工厂,实现类为 RequestObjectFactory
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);
}
}
从上面的代码实现可见,应用上下文启动过程中,往容器中增加了用于生成ServletRequest
bean
对象的工厂bean RequestObjectFactory
。这正是bean HomeController
能注册成功的原因。
相关文章
Spring MVC : ServletRequest 注入原理分析
Spring MVC : ServletRequest 注入原理分析 2 - 为什么可以正确访问?