SpringMVC容器
之前分析了过 Spring 的启动过程了,今天看下 SpringMVC 的启动。一样的,我们先看下 web.xml,SpringMVC 是以 Servlet 配置出现的
1 | <context-param> |
之前分析了 ContextLoaderListener,实例化 IoC 容器,并将此容器实例注册到 ServletContext 中。我们先看下 DispatcherServlet 的类图及继承关系:
SpringMVC最核心的类就是 DispatcherServlet, 关于 Spring Context 的配置文件加载和创建是在 init() 方法中进行的,主要的调用顺序是 init–>initServletBean–>initWebApplicationContext 。 先来看一下 initWebApplicationContext 的实现:FrameworkServlet.java
先简单说下这些代码的功能:
514行:从 ServletContext 中获取 rootContext也就是SpringIOC容器
517行:如果一个 context 的实例被注入了,直接使用
538行:从 ServletContext 中获取 webApplicationContext也就是SpringMVC容器
543行:创建 SpringMVC 的容器,并将 rootContext 作为父容器
550行:刷新上下文(执行组件的初始化),这个方法由子类DispatchServlet的方法实现
556行:将 SpringMVC 容器作为属性设置进 ServletContext
这里多说一句,SpringMVC 容器在 ServletContext 中的属性名:1
2
3
4
5public String getServletContextAttributeName() {
return SERVLET_CONTEXT_PREFIX + getServletName();
}
public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";
而 SpringIOC 容器在 ServletContext 中的属性名:1
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
前面的没什么好说的,我们看下 onRefresh() 方法,调用了 initStrategies() 方法:
执行 MVC 的相关组件的初始化,我们以 HandlerMappings 为例看来看下:
detectAllHandlerMappings 默认为 true,从当前的 SpringMVC 容器及其父容器中查找所有的 HandlerMappings,否则只从当前的 SpringMVC 容器中查找 HandlerMapping,如果没有找到 handlerMappings,设置默认的 handlerMapping,默认值设置在 DispatcherServlet 同级目录的 DispatcherServlet.properties 中。
多说一句
上面的 findWebApplicationContext(),createWebApplicationContext(rootContext) 之类的方法点进去看看也很容易懂,我就不贴源码了,然后 createWebApplicationContext 中会层层调用直到 AbstractApplicationContext的 refresh 方法来初始化 bean,这个方法在之前分析 Spring 启动的时候看过,这里也就不看了。
还是那句话,以我现在水平分析源码并不指望能看懂并理解每一句每一行,但是看不懂的方法你就点进去看看,万一里面里面的东西你看过呢是不是,就怕看不懂然后觉得这行代码不重要就不看了。
嗯?说完了?怎么感觉看完之前的Spring容器那点事,再看这个好像也没什么了。我们再来简单说下 Spring 容器和 SpringMVC 容器的py(手动滑稽)关系。
Spring容器 和 SpringMVC容器的关系
ContextLoaderListener 中创建 ApplicationContext(SpringIOC容器)主要用于整个 Web 应用程序需要共享的一些组件 ,比如 DAO,数据库的 ConnectionFactory 等。而由 DispatcherServlet 创建的 ApplicationContext(SpringMVC容器)主要用于和该 Servlet 相关的一些组件 ,比如 Controller、ViewResovler 等。
对于作用范围而言, 在 DispatcherServlet 中可以引用由 ContextLoaderListener 所创建的 ApplicationContext ,而反过来不行。
在 Spring 的具体实现上,这两个 ApplicationContext 都是通过 ServletContext 的 setAttribute 方法放到 ServletContext 中的。但是, ContextLoaderListener 会先于 DispatcherServlet 创建 ApplicationContext,DispatcherServlet 在创建 ApplicationContext 时会先找到由 ContextLoaderListener 所创建的 ApplicationContext,再将后者的 ApplicationContext 作为参数传给 DispatcherServlet 的 ApplicationContext 的 setParent() 方法,1
wac.setParent(parent);
其中, wac 即为由 DisptcherServlet 创建的 ApplicationContext,而 parent 则为 ContextLoaderListener 创建的ApplicationContext 。此后,框架又会调用 ServletContext 的 setAttribute() 方法将 wac 加入到 ServletContext 中。
当 Spring 在执行 ApplicationContext 的 getBean 时, 如果在自己 context 中找不到对应的 bean,则会在父容器中去找 。这也解释了为什么我们可以在 DispatcherServlet 中获取到由 ContextLoaderListener 对应的 ApplicationContext 中的 bean。举个例子就是,你可以在 controller 层中注入 service 层的 bean。