文章目录
- 【README】
- 【1】DelegatingFilterProxy回顾
- 【1.1】DelegatingFilterProxy初始化过滤器bean
- 【2】从servlet容器获取springmvc顶级web容器
- 【2.1】从Servlet容器中获取springmvc容器总结
- 【2.2】ContextLoaderListener加载springmvc顶级web容器并将其添加到servlet容器
- 【2.2.1】ContextLoader-springmvc上下文加载器
- 【2.2.2】springmvc容器分类(springmvc顶级web容器与次顶级web容器)
- 【3】DispatcherServlet初始化springmvc次顶级web容器(二级容器)
- 【3.1】DispatcherServlet定义
- 【3.1.1】创建二级web容器
- 【3.1.2】应用初始化器到springmvc二级容器
【README】
本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;
代码详情参见: springmvcDiscoverFirstDemo【github】
1)本文根据springmvc应用加载时读取的配置文件,介绍servlet容器与springmvc容器;
- servlet容器与springmvc容器是两种不同容器,它们各自读取的配置文件不同;且servlet容器先于springmvc容器被发明;
- 为什么要引入这个知识点,还得从DelegatingFilterProxy说起;
2)此外:springmvc容器进一步分类(重要): springmvc顶级web容器与二级web容器 ;
- springmvc顶级web容器:ContextLoaderListener加载的spring web容器;
- springmvc次顶级(二级)web容器:servlet加载的spring web容器 ,如DispatcherServlet;
- 次顶级(二级)web容器把顶级web容器作为父类, 即springmvc次顶级web容器可以使用顶级web容器的bean装配自身bean ;
3)所以总结起来:springmvc的web应用加载了3种容器(能够理解到这一点,非常重要) :
- servlet容器;
- 加载时读取web.xml配置文件;
- springmvc顶级web容器; (所有二级web容器的父亲) ;
- 加载时读取web.xml中contextConfigLocation属性指定的xml文件;如本文中的 applicationContext.xml , applicationContext-module1.xml ;
- 初始化完成后,作为属性添加到servlet容器,属性名= org.springframework.web.context.WebApplicationContext.ROOT ;
- springmvc二级web容器(次顶级web容器,把顶级web容器作为父亲); (servlet级别的容器,即每个servlet都可以拥有自身的容器)
- 加载时读取web.xml中各servlet元素中contextConfigLocation属性指定的xml文件; 如本文中的dispatcher-servlet.xml , dispatcher-servlet2.xml , dispatcher-servlet3-upload.xml ;
- 初始化完成后,作为属性添加到servlet容器,属性名= org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher;
4)目录结构:
5)web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns = "https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version = "5.0"
metadata-complete = "false"
>
<display-name>springmvcDiscover</display-name>
<!-- 指定ContextLoaderListener加载web容器时使用的多个xml配置文件(默认使用/WEB-INF/applicationContext.xml) -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml,/WEB-INF/applicationContext-module1.xml</param-value>
</context-param>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 注册过滤器代理 -->
<filter>
<filter-name>customFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>customFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置监听器ContextLoaderListener,其加载顶层WebApplicationContext web容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 注册一级控制器 DispatcherServlet,用于拦截所有请求(匹配url-pattern) -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- DispatcherServlet启动读取xml配置文件加载组件,构建web容器(子),通过contextConfigLocation为其配置多个xml文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml,/WEB-INF/dispatcher-servlet2.xml,/WEB-INF/dispatcher-servlet3-upload.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
<!-- 新增multipart-config 子元素,该servlet才启用文件上传功能(必须) -->
<multipart-config>
<!-- 当上传文件被处理或文件超过fileSizeThreshold,文件的保存路径;默认为空串 -->
<location>D:\temp\springmvcUploadDir</location>
<!-- 上传文件字节最大值,若超过则抛出异常;默认无限;我们这里设置为20M -->
<max-file-size>20971520</max-file-size>
<!-- 请求报文字节最大值,若超过则抛出异常;默认无限;我们这里设置为1000M -->
<max-request-size>1048576000</max-request-size>
<!-- 临时保存到磁盘的文件大小最小值(超过该值就保存);默认0 -->
<file-size-threshold>-1</file-size-threshold>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
【1】DelegatingFilterProxy回顾
1)问题: 为什么要在web.xml中注册DelegatingFilterProxy ;
- DelegatingFilterProxy的作用:作为Filter的代理对象;当对请求拦截时,把拦截逻辑委派给具体的Filter(如CustomFilter);
- 物理结构上DelegatingFilterProxy在web.xml中注册,在servlet容器中;
- 而 CustomFilter 在 applicationContext.xml 中注册,在springmvc顶级WebApplicationContext容器中;
- 当然,我们讲,在web.xml中肯定可以注册CustomFilter来执行拦截逻辑;但无法装配spring容器的bean;
- 简单理解: 要把spring容器的bean装配到CustomFilter,则CustomerFilter必须注册到spring容器; 所以CustomerFilter在applicationContext.xml中注册(applicationContext.xml是springmvc顶级web容器加载的配置文件)
- 又引入新问题:把CustomFilter注册到spring的顶级web容器中,servlet容器是无法识别的;由上文可知,filter是servlet级别的拦截,又servlet容器无法识别spring容器中的CustomFilter,所以如果没有中介,servlet容器是无法调用spring容器中的CustomFilter执行过滤逻辑 ;
- 解决方法: DelegatingFilterProxy 就是连接servlet容器与spring容器的中介;DelegatingFilterProxy在web.xml中配置,注册到servlet容器,servlet容器执行DelegatingFilterProxy的doFilter()方法,doFilter方法内部根据filter名称从spring容器中取出目标filter并执行目标filter的过滤逻辑;
2)详情参见 spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理) 中【3.3】章节 ;
- 这也由此引出了 Servlet容器与springmvc容器;
【1.1】DelegatingFilterProxy初始化过滤器bean
【DelegatingFilterProxy#initFilterBean()】 从springmvc容器中查找注册的过滤器bean
// 初始化过滤器bean
protected void initFilterBean() throws ServletException {
synchronized(this.delegateMonitor) {
if (this.delegate == null) {
if (this.targetBeanName == null) {
// 获取目标过滤器名称(默认是DelegatingFilterProxy在web.xml中注册的名称)
this.targetBeanName = this.getFilterName();
}
// 获取spring的web容器,并通过web容器初始化过滤器(委派的过滤器)
WebApplicationContext wac = this.findWebApplicationContext();
if (wac != null) {
this.delegate = this.initDelegate(wac);
}
}
}
}
// 查找spring Web容器(顶级web容器)
protected WebApplicationContext findWebApplicationContext() {
if (this.webApplicationContext != null) {
WebApplicationContext var2 = this.webApplicationContext;
if (var2 instanceof ConfigurableApplicationContext) {
ConfigurableApplicationContext cac = (ConfigurableApplicationContext)var2;
if (!cac.isActive()) {
cac.refresh();
}
}
return this.webApplicationContext;
} else { // 走else分支
String attrName = this.getContextAttribute();
return attrName != null ? WebApplicationContextUtils.getWebApplicationContext(this.getServletContext(), attrName) : WebApplicationContextUtils.findWebApplicationContext(this.getServletContext());
// 走 WebApplicationContextUtils.findWebApplicationContext
}
}
//
【WebApplicationContextUtils】 WebApplicationContextUtils.findWebApplicationContext() 查找web容器;
public static WebApplicationContext findWebApplicationContext(ServletContext sc) {
// 通过ServletContext(servlet容器)获取springWeb容器
WebApplicationContext wac = getWebApplicationContext(sc);
if (wac == null) {
Enumeration<String> attrNames = sc.getAttributeNames();
while(attrNames.hasMoreElements()) {
String attrName = (String)attrNames.nextElement();
Object attrValue = sc.getAttribute(attrName);
if (attrValue instanceof WebApplicationContext) {
// spring的web容器实际作为ServletContext的属性值,被获取后,通过类型转换,得到WebApplicationContext类型的spring的web容器
WebApplicationContext currentWac = (WebApplicationContext)attrValue;
if (wac != null) {
throw new IllegalStateException("No unique WebApplicationContext found: more than one DispatcherServlet registered with publishContext=true?");
}
wac = currentWac;
}
}
}
return wac;
}
// 通过ServletContext(servlet容器)获取springWeb容器
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
// WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = org.springframework.web.context.WebApplicationContext.ROOT
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
// 获取springWeb容器
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
Assert.notNull(sc, "ServletContext must not be null");
// 根据属性名称(org.springframework.web.context.WebApplicationContext.ROOT) 从Servlet容器中获取SpringWeb容器
Object attr = sc.getAttribute(attrName);
if (attr == null) {
return null;
} else if (attr instanceof RuntimeException) {
RuntimeException runtimeException = (RuntimeException)attr;
throw runtimeException;
} else if (attr instanceof Error) {
Error error = (Error)attr;
throw error;
} else if (attr instanceof Exception) {
Exception exception = (Exception)attr;
throw new IllegalStateException(exception);
} else if (attr instanceof WebApplicationContext) {
// 若名=org.springframework.web.context.WebApplicationContext.ROOT的属性值的类型是WebApplicationContext,则该属性值是spring的web容器
WebApplicationContext wac = (WebApplicationContext)attr;
return wac;
} else {
throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
}
}
【WebApplicationContext】springmvc容器继承自 spring容器 ApplicationContext
public interface WebApplicationContext extends ApplicationContext {
// org.springframework.web.context.WebApplicationContext.ROOT
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
String SCOPE_REQUEST = "request";
String SCOPE_SESSION = "session";
String SCOPE_APPLICATION = "application";
String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";
@Nullable
ServletContext getServletContext();
}
【2】从servlet容器获取springmvc顶级web容器
1)WebApplicationContextUtils.getWebApplicationContext()调试现场: 显然, servlet容器中存在名=org.springframework.web.context.WebApplicationContext.ROOT的属性值,这个属性值就是springmvc容器,类型是XmlWebApplicationContext ;
2)DelegatingFilterProxy获取springweb容器(或springmvc容器)后, 调用 initDelegate(WebApplicationContext wac) 方法,从springmvc容器中通过beanName获取具体Filter,并暂存到this.delegate;
- 由运行时对象可知, springmvc容器类型是XmlWebApplicationContext , 其configLocations的值就是web.xml中配置的contextConfigLocation的值;该值被org.springframework.web.context.ContextLoaderListener读取并初始化springmvc容器(spring的web容器);
【2.1】从Servlet容器中获取springmvc容器总结
1)ServletContext抽象了servlet容器, WebApplicationContext抽象了springmvc容器;
- 当然,本文通过web.xml部署web应用,所以springmvc容器的实际类型为XmlWebApplicationContext,它是WebApplicationContext的子类;
2)servlet容器如何与springmvc容器产生关联?
- 由上文可知,springmvc容器是作为servlet容器的一个属性值而存在的(属性名为org.springframework.web.context.WebApplicationContext.ROOT) ;
- 问题: 谁把springmvc容器作为一个属性添加到servlet容器? – ContextLoaderListener 上下文加载器监听器
【2.2】ContextLoaderListener加载springmvc顶级web容器并将其添加到servlet容器
1)ContextLoaderListener定义:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
// 上下文(容器)初始化
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
-
ContextLoaderListener的全限定类名为org.springframework.web.context.ContextLoaderListener ,它是springmvc定义的,继承了springmvc的ContextLoader,且实现了servlet的ServletContextListener接口,以便servlet容器启动时调用ServletContextListener的contextInitialized() 初始化web容器;
-
显然,servlet容器启动时调用ServletContextListener的contextInitialized() ,而ContextLoaderListener#contextInitialized()调用了ContextLoader#initWebApplicationContext()
【2.2.1】ContextLoader-springmvc上下文加载器
1)ContextLoader初始化springmvc容器
【web.xml】
<!-- 指定ContextLoaderListener加载web容器时使用的多个xml配置文件(默认使用/WEB-INF/applicationContext.xml) -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml,/WEB-INF/applicationContext-module1.xml</param-value>
</context-param>
【ContextLoader-initWebApplicationContext()】 初始化顶级web容器,其加载的配置文件是web.xml中contextConfigLocation元素配置的xml路径(/WEB-INF/applicationContext.xml,/WEB-INF/applicationContext-module1.xml);
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!");
} else {
// ...
try {
if (this.context == null) {
// 这里仅仅是通过反射创建XmlWebApplicationContext实例(1个空容器),赋值给xontext
this.context = this.createWebApplicationContext(servletContext);
}
WebApplicationContext var6 = this.context;
if (var6 instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)var6;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = this.loadParentContext(servletContext);
cwac.setParent(parent);
}
// 真正初始化springmvc容器
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// springmvc容器初始化完成后,作为属性添加到servletContext(servlet容器)
// WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE值等于org.springframework.web.context.WebApplicationContext.ROOT
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);
}
// ...
return this.context;
} catch (Error | RuntimeException var8) {
logger.error("Context initialization failed", var8);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
throw var8;
}
}
}
显然,通过上述代码【servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context)】可知,initWebApplicationContext()方法把springmvc容器作为ServletContext的属性添加到ServletContext ;
【ContextLoader-configureAndRefreshWebApplicationContext(WebApplicationContext, ServletContext)】配置与刷新web容器
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
String configLocationParam;
// ...
wac.setServletContext(sc);
// 获取web.xml中配置的contextConfigLocation属性,属性值是xml文件路径,该xml用于加载springmvc容器
configLocationParam = sc.getInitParameter("contextConfigLocation");
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam); // 把springmvc容器需要加载的xml文件路径设置到springmvc容器本身
}
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment cwe) {
cwe.initPropertySources(sc, (ServletConfig)null);
}
this.customizeContext(sc, wac);
// refresh()方法才是真正初始化springmvc容器(注入bean,装配bean等,这里不展开)
wac.refresh();
}
【2.2.2】springmvc容器分类(springmvc顶级web容器与次顶级web容器)
1)springmvc顶级容器:通过ContextLoaderListener监听器初始化的springmvc容器是顶级web容器;
2)springmvc二级容器(次顶级容器): 各个Servlet(如DispatcherServlet)初始化的springmvc容器是二级容器或次顶级容器,springmvc二级容器把顶级容器作为父类,前者可以使用后者的bean用于装配自身bean ;
【3】DispatcherServlet初始化springmvc次顶级web容器(二级容器)
1)加载时读取web.xml中各servlet元素中contextConfigLocation属性指定的xml文件, 也即本文中的/WEB-INF/dispatcher-servlet.xml,/WEB-INF/dispatcher-servlet2.xml,/WEB-INF/dispatcher-servlet3-upload.xml;
【web.xml】 配置DispatcherServlet元素
<!-- 注册一级控制器 DispatcherServlet,用于拦截所有请求(匹配url-pattern) -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- DispatcherServlet启动读取xml配置文件加载组件,构建web容器(子),通过contextConfigLocation为其配置多个xml文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml,/WEB-INF/dispatcher-servlet2.xml,/WEB-INF/dispatcher-servlet3-upload.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
<!-- 新增multipart-config 子元素,该servlet才启用文件上传功能(必须) -->
<multipart-config>
<!-- 当上传文件被处理或文件超过fileSizeThreshold,文件的保存路径;默认为空串 -->
<location>D:\temp\springmvcUploadDir</location>
<!-- 上传文件字节最大值,若超过则抛出异常;默认无限;我们这里设置为20M -->
<max-file-size>20971520</max-file-size>
<!-- 请求报文字节最大值,若超过则抛出异常;默认无限;我们这里设置为1000M -->
<max-request-size>1048576000</max-request-size>
<!-- 临时保存到磁盘的文件大小最小值(超过该值就保存);默认0 -->
<file-size-threshold>-1</file-size-threshold>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
【3.1】DispatcherServlet定义
1)DispatcherServlet继承FrameworkServlet ;
public class DispatcherServlet extends FrameworkServlet {
//...
}
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
// ...
}
2)servlet容器加载时,会调用servlet的init()方法进行初始化,如下;
【FrameworkServlet#initServletBean()】 初始化servlet bean
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
// 初始化web容器
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
// ...
}
// 初始化web容器
protected WebApplicationContext initWebApplicationContext() {
// rootContext 就是springmvc顶级web容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
// wac才是DispatcherServlet加载的springmvc二级web容器
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext cwac && !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) {
// 若有现成的二级web容器,则直接获取
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 若没有现成的二级web容器,则创建二级web容器
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) {
// 这里才是真正读取二级web容器对应的xml文件,并实例化对应spring bean的地方
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
// 把二级web容器作为servlet容器的属性设置到servlet容器 (这与springmvc顶级web容器类似,也是作为servlet容器的属性被引用)
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
【补充】FrameworkServlet初始化当前Servlet的web容器后,把该web容器作为属性添加到servlet容器 ;
- 如 DispatcherServlet对应的web容器,其属性名为 org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher
【3.1.1】创建二级web容器
1)首先获取springmvc顶级web容器, 然后判断是否有现成的该Servlet的二级web容器(DispatcherServlet),若有直接返回; 若没有,则调用createWebApplicationContext()方法创建二级web容器;
【FrameworkServlet#initWebApplicationContext()】
【FrameworkServlet#createWebApplicationContext(WebApplicationContext parent)】
- 把rootContext(springmvc顶级web容器)传入createWebApplicationContext()方法,创建二级web容器 ;
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
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");
}
// 通过反射创建DispatcherServlet的web容器
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
// 获取该web容器的xml配置文件(spring容器xml配置文件)
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 根据configLocation指定的xml配置文件,初始化DispatcherServlet的web容器
configureAndRefreshWebApplicationContext(wac);
return wac;
}
【代码解说】
configLocation就是web.xml中DispatcherServlet元素中configLocation嵌套元素指定的xml配置文件,如下;
【FrameworkServlet#configureAndRefreshWebApplicationContext()】根据configLocation指定的xml配置文件,初始化DispatcherServlet的web容器
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment cwe) {
cwe.initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac); // 应用初始化器
wac.refresh(); // 加载xml配置文件,实例化bean并正常到当前容器
}
【3.1.2】应用初始化器到springmvc二级容器
【FrameworkServlet#applyInitializers()】
protected void applyInitializers(ConfigurableApplicationContext wac) {
String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
if (globalClassNames != null) {
for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
this.contextInitializers.add(loadInitializer(className, wac));
}
}
if (this.contextInitializerClasses != null) {
for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
this.contextInitializers.add(loadInitializer(className, wac));
}
}
AnnotationAwareOrderComparator.sort(this.contextInitializers);
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac); // 调用初始化器
}
}