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); // 调用初始化器
    }
}
相关推荐
gys989510 小时前
(IDEA)spring项目导入本地jar包方法和项目打包时找不到引入本地jar包的问题解决方案
spring·intellij-idea·jar
vonlinee10 小时前
Spring系列 AOP实现过程
java·后端·spring
骆晨学长10 小时前
基于SSM的电影院订票系统设计与实现
java·开发语言·spring boot·后端·spring·推荐算法
荆州克莱12 小时前
GPT与大模型行业落地实践探索
spring boot·spring·spring cloud·css3·技术
牧小七13 小时前
SpringCloud 2024常用组件
后端·spring·spring cloud
别挡13 小时前
SpringCloud Config配置中心 SpringCloud Bus消息总线
后端·spring·spring cloud
小小娥子1 天前
Spring的热部署工具和数据库密码加盐操作
java·开发语言·spring boot·spring
小林想被监督学习1 天前
MultipartFile 接口
java·后端·spring
shigen011 天前
借助spring的IOC能力消除条件判断
java·spring