首先先看下经典的web.xml的配置文件:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>*.form</url-pattern> </servlet-mapping> </web-app>
我们知道tomcat在启动的时候会加载这个web.xml配置文件,加载玩之后会按照listener,filter,servlet的顺序进行实例化和初始化的操作(这个顺序需要看tomcat源码),那么对于上述这个配置文件解析之后会先加载org.springframework.web.context.ContextLoaderListener,这个类实现了
javax.servlet.ServletContextListener这个接口,那么一定会调用对应的javax.servlet.ServletContextListener#contextInitialized这个方法,而这个方法在org.springframework.web.context.ContextLoaderListener中的实现如下:
@Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
initWebApplicationContext会调用到org.springframework.web.context.ContextLoader#initWebApplicationContext
在这个方法中会调用org.springframework.web.context.ContextLoader#createWebApplicationContext来创建spring容器对象,并在后续进行refresh:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ContextLoader* definitions in your web.xml!"); } servletContext.log("Initializing Spring root WebApplicationContext"); Log logger = LogFactory.getLog(ContextLoader.class); if (logger.isInfoEnabled()) { logger.info("Root WebApplicationContext: initialization started"); } long startTime = System.currentTimeMillis(); try { // Store context in local instance variable, to guarantee that // it is available on ServletContext shutdown. if (this.context == null) {//创建容器对象 this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; 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 -> // determine parent for root web application context, if any. ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } //刷新容器,会调用到spring容器启动的时候经典的refresh方法 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); } if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms"); } return this.context; } catch (RuntimeException | Error ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } }
接下来就是加载springmvc子容器了,也就是org.springframework.web.servlet.DispatcherServlet的初始化,它继承了org.springframework.web.servlet.FrameworkServlet,servlet加载的顺序此处不作赘述,org.springframework.web.servlet.FrameworkServlet在初始化的时候会调用org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext这个方法来初始化springmvc子容器:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
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
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
其中标红的这一行代码就是获取前面已经创建好的spring服容器,但是不明白为什么this.webApplicationContext这个属性去判断是否需要创建子容器,这个属性在FrameworkServlet里面只有两个方法会进行set值:
public void setApplicationContext(ApplicationContext applicationContext) { if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) { this.webApplicationContext = (WebApplicationContext) applicationContext; this.webApplicationContextInjected = true; } }
public FrameworkServlet(WebApplicationContext webApplicationContext) { this.webApplicationContext = webApplicationContext; }
但是很遗憾,在springmvc项目中打断点发现都没有调用,在springboot中则会调用setApplicationContext这个方法
这个属性如果为空的话,就会调用org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.web.context.WebApplicationContext)去创建一个子容器,并将子容器里面的parant属性设置为rootContext
因为有了父子容器的关系,所以我们在springmvc项目中通常要讲controller注入到springmvc子容器,不能注入到spring父容器中,否则会出现404,而service,dao层则只需要注入到spring容器
但是现在大家普遍用的比较多的是springboot,我们在配置的时候也没要求controller只能注入到子容器中,所以我猜测在springboot中应该不再有父子容器的概念,最终在跟踪源码的时候发现确实如此,springboot中子容器直接复用父容器,不会创建子容器,也就是在前面提到的this.webApplicationContext这个属性不为空,因为org.springframework.web.servlet.FrameworkServlet实现了org.springframework.context.ApplicationContextAware这个接口,这个接口我想大家都很熟悉,而这个接口的实现,在spring容器启动过程中,通过BeanpostProcessor来调用org.springframework.context.ApplicationContextAware#setApplicationContext这个方法的填充applicationcontext,FrameworkServlet实现如下:
public void setApplicationContext(ApplicationContext applicationContext) { if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) { this.webApplicationContext = (WebApplicationContext) applicationContext; this.webApplicationContextInjected = true; } }
总结:
springmvc启动过程:
tomcat启动,加载web.xml-》加载org.springframework.web.context.ContextLoaderListener创建spring父容器-》加载org.springframework.web.servlet.DispatcherServlet创建springmvc子容器
springboot启动过程:
spring容器启动-》加载org.springframework.web.servlet.DispatcherServlet,通过org.springframework.context.ApplicationContextAware#setApplicationContext直接复用已经创建好的spring容器
最后,没想明白的是在org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext这个方法中为什么不直接复用这个方法第一行取到的那个rootContext(已经创建好的spring容器),而是要单独通过
private WebApplicationContext webApplicationContext
这么一个属性来判断