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); // 调用初始化器
    }
}
相关推荐
有梦想的攻城狮1 小时前
spring中的@MapperScan注解详解
java·后端·spring·mapperscan
柚个朵朵2 小时前
Spring的Validation,这是一套基于注解的权限校验框架
java·后端·spring
程序员小杰@2 小时前
【MCP教程系列】SpringBoot 搭建基于 Spring AI 的 SSE 模式 MCP 服务
人工智能·spring boot·spring
程序员buddha3 小时前
Spring & Spring Boot 常用注解整理
java·spring boot·spring
C_V_Better3 小时前
Java Spring Boot 控制器中处理用户数据详解
java·开发语言·spring boot·后端·spring
LUCIAZZZ5 小时前
JVM之虚拟机运行
java·jvm·spring·操作系统·springboot
神秘的t6 小时前
Spring Web MVC————入门(2)
java·spring·mvc
冷心笑看丽美人6 小时前
Spring MVC数据绑定和响应 你了解多少?
java·spring·mvc
蒂法就是我9 小时前
详细说说Spring的IOC机制
java·后端·spring
唐僧洗头爱飘柔952710 小时前
【SSM-SpringMVC(二)】Spring接入Web环境!本篇开始研究SpringMVC的使用!SpringMVC数据响应和获取请求数据
java·spring·文件上传·页面跳转·数据响应·获取请求数据·静态资源访问