Spring IoC 学习 ——ApplicationContext 与 DispatcherServlet
最近在新项目中接触到 Spring MVC,在学习过程中对 Spring IoC 的配置产生了一些疑问。通过同事的指点,和查阅资料,现在初步对其做一点总结归纳:
在 Spring Web 应用中,有两种不同 IoC 容器的实现方式:ApplicationContext 与 WebApplicationContext。
其中,对于每一个 Web 应用,都只有唯一一个对应的 ApplicationContext。ApplicationContext 是由 ContextLoaderListener 来实现的,通过如下方式在 web.xml 中添加以下监听器:
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param>
![](https://images2015.cnblogs.com/blog/885888/201601/885888-20160124152332609-1094997522.png)
由于 ContextLoaderListener 实现了 ServletContextListener 接口,所以 ContextLoaderListener 会在 Web 容器启动时,默认执行它实现的方法,通过调用 ContextLoader.initWebApplicationContext() 自动装配 applicationContext.xml 中的配置,构建起一个在整个 web 应用生命周期中都可以获取到的上下文。这个上下文 (也可以理解成一个 IoC 容器) 中,一般都会包含对一些中层组件 (middle-tier component) 的定义,如 Service/Dao 层的组件,或者一些可能在应用的各个部分用到的 Object。
WebApplicationContext 继承自 ApplicationContext,可以看做是 ApplicationContext 的一个子集。Spring Web 应用中每个 DispatcherServlet 都对应一个独立的 WebApplicationContext。 当ContextLoaderListener 和 DispatcherServlet 一起使用时,ContextLoaderListener 会先创建一个根 ApplicationContext,然后 DispatcherServlet 创建的 WebApplicationContext 则会绑定到根 ApplicationContext 之下。
web.xml 中配置如下:
<servlet> <servlet-name>spring-mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
所以也就不难理解为什么在 ApplicationContext 中定义的 bean 可以在每个 WebApplicationContext 中使用。
通常为了将 Web 层模块与底层业务模块分离,提供更高细粒度的注入控制,在 WebApplicationContext 中只会定义 web 相关的模块,如 Controller, ViewResolver 或 handlers。(每个 DispatcherServlet 是相互独立的)
在上面我们提到,比较受推崇的做法是将不同的模块交由不同的 ApplicationContext 来管理。那么在 web.xml 中我们需要做如下的配置:
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <servlet> <servlet-name>spring-mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
在 spring-mvc-servlet 中:
<context:component-scan base-package="com.zhao.controllers"> <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/> </context:component-scan>
在 applicationContext.xml 中:
<context:component-scan base-package="com.zhao.services"> <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/> </context:component-scan>
但其实在实际使用中我们并不一定需要遵守这个条约。尤其是对于较小的项目,很多时候其实仅靠 DispatchServlet 就可以很好地管理我们的 IoC 容器。这种情况下我们仅需要在 web.xml 中做如下配置:
<servlet> <servlet-name>spring-mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
在 spring-mvc-servlet 中:
<context:component-scan base-package="com.zhao"></context:component-scan>
这样项目的配置看起来会简洁的多,而且统一放置的配置文件也会方便我们管理。
当然,Spring Framework 为我们提供了两种 ApplicationContext 自有他的道理,在以下一些情况中,我们还是需要对不同的 context 分别进行配置:
- 应用中包含的多个 DispatcherServlet 需要共享某个业务操作时
- 应用中包含的非 Spring servlets 需要注入某个业务操作时
所以在实际应用中,还是需要灵活思考,选择最合适的配置方式。不能总是期望能找到 silver bullet,一成不变的解决问题 : )