spring揭秘25-springmvc04-servlet容器与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 复制代码
<?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

java 复制代码
// 初始化过滤器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容器;

java 复制代码
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

java 复制代码
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定义:

java 复制代码
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】

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);

java 复制代码
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容器

java 复制代码
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元素

xml 复制代码
<!-- 注册一级控制器 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 ;

java 复制代码
public class DispatcherServlet extends FrameworkServlet {
    //...
}

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    // ... 
}

2)servlet容器加载时,会调用servlet的init()方法进行初始化,如下;

【FrameworkServlet#initServletBean()】 初始化servlet bean

java 复制代码
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容器 ;
java 复制代码
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容器

java 复制代码
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()】

java 复制代码
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); // 调用初始化器
    }
}
相关推荐
撒呼呼2 小时前
# 起步专用 - 哔哩哔哩全模块超还原设计!(内含接口文档、数据库设计)
数据库·spring boot·spring·mvc·springboot
天使day3 小时前
SpringMVC
java·spring·java-ee
壹佰大多5 小时前
【spring-cloud-gateway总结】
java·spring·gateway
CodeChampion5 小时前
60.基于SSM的个人网站的设计与实现(项目 + 论文)
java·vue.js·mysql·spring·elementui·node.js·mybatis
秋意钟5 小时前
Spring框架处理时间类型格式
java·后端·spring
科马9 小时前
【Redis】缓存
数据库·redis·spring·缓存
cloud___fly9 小时前
Spring AOP入门
java·后端·spring
zxguan11 小时前
Springboot 学习 之 logback-spring.xml 日志压缩 .tmp 临时文件问题
spring boot·学习·spring
繁川11 小时前
深入理解Spring AOP
java·后端·spring
AI人H哥会Java19 小时前
【Spring】Spring的模块架构与生态圈—Spring MVC与Spring WebFlux
java·开发语言·后端·spring·架构