Spring 和 Spring MVC 的 IOC 之间容器关系
概述
上图是引用 Spring 官方网站的图片。Spring MVC 的 IOC 容器是在 Spring 的 IOC 容器延深展开来的。
首先分析配置文件 web.xml
<web-app>
<!-- Spring 配置 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<!-- Spring MVC 配置 -->
<servlet>
<servlet-name>app1</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app1-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<url-pattern>/app1/*</url-pattern>
</servlet-mapping>
</web-app>
上面是经典的 Spring 和 Spring MVC 在 web.xml 文件。
Spring 容器初始化
为了配置 Spring 的初始化,指定了一个 ContextLoaderListener 监听器,它的原本也是在 ServletContextListener 实现上来的,可以查看源码
ContextLoaderListener.java
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
//初始化加载方法
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
//销毁加载方法
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
我们知道,在监听器中 contextInitialized
方法和 contextDestroyed
方法是必须执行的。而 ContextLoaderListener 就实现了这两个方法,并且在初始化方法 contextInitialized
调用了 initWebApplicationContext
方法来初始化 WebApplicationContext 容器。我们坠入 initWebApplicationContext
一看究竟。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
...
try {
//如果 context 为空,那么创建 xmlApplicationContext
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//如果容器没有被刷新过
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//刷新容器并且配置容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
...
}
}
该方法是由 ContextLoaderListener 的父类 ContextLoader 实现。那么为什么是 xmlApplicationContext。查看 createWebApplicationContext
方法,内部又调用了 determineContextClass
方法
protected Class<?> determineContextClass(ServletContext servletContext) {
//用来获取自定义的 applicationContext 的类,没有就从 defaultStrategies 读取配置文件内容
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
我们在看看 defaultStrategies 如何初始化
private static final Properties defaultStrategies;
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
//加载了 resource 的配置文件
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
它加载了一个配置文件,这里来就直接给配置文件的内容 ContextLoader.properties
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
到这里懂了吧,如果没有指定 contextClass 的话,会加载 ContextLoader.properties 资源文件的 webApplicationContext 所指定的类。这也就是为什么默认是 xmlwebApplicationContext 的实现类,而不是其他的。
回到最开始的 initWebApplicationContext
的方法,其中有一个 configureAndRefreshWebApplicationContext
方法来加载配置文件内容,追进去查看
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
...
//加载配置文件的参数
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
...
}
这里就不多说了,通过 MockServletContext 实现类的 getInitParameter
方法获取了 配置文件的信息。
到这一步就懂了吧,我们在 web.xml 配置的信息,init-param 中的 contextConfigLocation 就是告诉 ContextLoaderListener ,Spring 的配置文件在哪,而我们没有配置 contextClass,所以就默认采用了 XMLWebApplicationContext。
Spring MVC 容器初始化
说完了的 Spring,那么 Spring MVC 呢?
在 web.xml 还配置了 DispatcherServlet,而 DispatcherServlet 都干了啥?我们知道 Spring MVC 的 DispatcherServlet 拦截了所有的 Servlet 请求。那么 DispatcherServlet 本质是啥?
可以清楚的看到,它是继承了 HttpServlet,所以本质就是一个 Servlet , 它有着 Servlet 所应有的功能。通过断点追踪,发现代码会先走到 HttpServletBean 的 inti 方法。在 Servlet 的生命周期中,知道 init 的方法是初始化的方法,在生成对象的时候会执行,并且只执行一次,而 DispatcherServlet 是继承 HttpServletBean,查看 HttpServletBean 的 init 源码
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
.....
// 初始化 SerlvetBean
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
断点继续进入 initServletBean,该方法由 FrameworkServlet 类实现
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
...
try {
// 初始化 initWebApplication
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
...
}
}
这里断点跟踪又进入了 initWebApplicationContext,而它正是由我们的 DispatchedServlet 实现的。进入查看
protected WebApplicationContext initWebApplicationContext() {
//获取根容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
///在构造时如果注入一个上下文实例->使用它
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
//如果上下文还没有刷新
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
//如果父容器没有,设置父容器
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
//把跟容器设置给 Spring MVC 当父容器
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
// 这个 servlet 如果没有定义上下文实例,那么创建一个本地实例,把父容器传入
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
onRefresh(wac);
}
...
return wac;
}
查看断点内容,如果你只是配置了一个 Spring MVC 内容,没有监听器,rootContext 就为 null,
如果配置了监听器,那么就不是 null 了
之后,断点进入了 createWebApplicationContext ,注意这里的 IOC 容器就是 Spring MVC 的,跟 Spring (也就是前面讲的)IOC 容器是不同的,它是基于 Spring 的 IOC 容器之上,所以它们是包含关系(Spring 包含 Spring MVC 的容器)。查看 createWebApplicationContext 源码(由父类 FrameworkServlet 实现)
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
...
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 设置父容器
wac.setParent(parent);
// 获取配置文件的位置
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
//配置并且刷新 WebApplicationContext
configureAndRefreshWebApplicationContext(wac);
return wac;
}
到这里也就懂了把,如果没有 contexloaderListener的情况下,parent是空的。在有 contexloaderListener的情况下,发现parent不是空的。也就完成了包含关系。