## SpringMVC 容器 之前分析了过 Spring 的启动过程了,今天看下 SpringMVC 的启动。一样的,我们先看下 web.xml,SpringMVC 是以 Servlet 配置出现的 ``` <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 的类图及继承关系: <div align="center"> <img src="https://github.com/Jimmy2Angel/jimmy2angel.github.io/blob/master/img/SpringMVC/DispatcherServletClass.png?raw=true"/> </div> SpringMVC 最核心的类就是 DispatcherServlet, 关于 Spring Context 的配置文件加载和创建是在 init() 方法中进行的,主要的调用顺序是 init-->initServletBean-->initWebApplicationContext 。 先来看一下 initWebApplicationContext 的实现:FrameworkServlet.java <div align="center"> <img src="https://github.com/Jimmy2Angel/jimmy2angel.github.io/blob/master/img/SpringMVC/initWebApplicationContext1.png?raw=true"/> <img src="https://github.com/Jimmy2Angel/jimmy2angel.github.io/blob/master/img/SpringMVC/initWebApplicationContext2.png?raw=true"/> </div> 先简单说下这些代码的功能: 514 行:从 ServletContext 中获取 rootContext 也就是 SpringIOC 容器 517 行:如果一个 context 的实例被注入了,直接使用 538 行:从 ServletContext 中获取 webApplicationContext 也就是 SpringMVC 容器 543 行:创建 SpringMVC 的容器,并将 rootContext 作为父容器 550 行:刷新上下文(执行组件的初始化),这个方法由子类 DispatchServlet 的方法实现 556 行:将 SpringMVC 容器作为属性设置进 ServletContext 这里多说一句,SpringMVC 容器在 ServletContext 中的属性名: ``` public String getServletContextAttributeName() { return SERVLET_CONTEXT_PREFIX + getServletName(); } public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT."; ``` 而 SpringIOC 容器在 ServletContext 中的属性名: ``` String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"; ``` 前面的没什么好说的,我们看下 onRefresh() 方法,调用了 initStrategies() 方法: <div align="center"> <img src="https://github.com/Jimmy2Angel/jimmy2angel.github.io/blob/master/img/SpringMVC/DispatcherServlet-onRefresh.png?raw=true"/> </div> 执行 MVC 的相关组件的初始化,我们以 HandlerMappings 为例看来看下: <div align="center"> <img src="https://github.com/Jimmy2Angel/jimmy2angel.github.io/blob/master/img/SpringMVC/DispatcherServlet-initHandlerMappings.png?raw=true"/> </div> detectAllHandlerMappings 默认为 true,从当前的 SpringMVC 容器及其父容器中查找所有的 HandlerMappings,否则只从当前的 SpringMVC 容器中查找 HandlerMapping,如果没有找到 handlerMappings,设置默认的 handlerMapping,默认值设置在 DispatcherServlet 同级目录的 DispatcherServlet.properties 中。 ### 多说一句 上面的 findWebApplicationContext(),createWebApplicationContext(rootContext) 之类的方法点进去看看也很容易懂,我就不贴源码了,然后 createWebApplicationContext 中会层层调用直到 AbstractApplicationContext 的 refresh 方法来初始化 bean,这个方法在之前分析 Spring 启动的时候看过,这里也就不看了。 还是那句话,以我现在水平分析源码并不指望能看懂并理解每一句每一行,但是看不懂的方法你就点进去看看,万一里面里面的东西你看过呢是不是,就怕看不懂然后觉得这行代码不重要就不看了。 嗯?说完了?怎么感觉看完之前的 [Spring 容器那点事](https://jimmy2angel.github.io/2017/05/31/SpringIOC/),再看这个好像也没什么了。我们再来简单说下 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() 方法, ``` 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。