SpringMVC容器

SpringMVC容器

之前分析了过 Spring 的启动过程了,今天看下 SpringMVC 的启动。一样的,我们先看下 web.xml,SpringMVC 是以 Servlet 配置出现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring-application.xml
</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

之前分析了 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
5
public 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。