文章目录
- 【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-appxmlns = "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.ROOTreturn 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.ROOTString 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";@NullableServletContext 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个空容器),赋值给xontextthis.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.ROOTservletContext.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 itwac = 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, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent -> set// the root application context (if any; may be null) as the parentcwac.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 informationif (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 #refreshConfigurableEnvironment 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); // 调用初始化器}
}