spring揭秘25-springmvc04-servlet容器与springmvc容器总结

news2024/11/25 2:38:56

文章目录

  • 【README】
  • 【1】DelegatingFilterProxy回顾
    • 【1.1】DelegatingFilterProxy初始化过滤器bean
  • 【2】从servlet容器获取springmvc顶级web容器
    • 【2.1】从Servlet容器中获取springmvc容器总结
    • 【2.2】ContextLoaderListener加载springmvc顶级web容器并将其添加到servlet容器
      • 【2.2.1】ContextLoader-springmvc上下文加载器
      • 【2.2.2】springmvc容器分类(springmvc顶级web容器与次顶级web容器)
  • 【3】DispatcherServlet初始化springmvc次顶级web容器(二级容器)
    • 【3.1】DispatcherServlet定义
      • 【3.1.1】创建二级web容器
      • 【3.1.2】应用初始化器到springmvc二级容器

【README】

本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;
代码详情参见: springmvcDiscoverFirstDemo【github】

1)本文根据springmvc应用加载时读取的配置文件,介绍servlet容器与springmvc容器;

  • servlet容器与springmvc容器是两种不同容器,它们各自读取的配置文件不同;且servlet容器先于springmvc容器被发明;
  • 为什么要引入这个知识点,还得从DelegatingFilterProxy说起;

2)此外:springmvc容器进一步分类(重要): springmvc顶级web容器与二级web容器

  • springmvc顶级web容器:ContextLoaderListener加载的spring web容器;
  • springmvc次顶级(二级)web容器:servlet加载的spring web容器 ,如DispatcherServlet;
    • 次顶级(二级)web容器把顶级web容器作为父类, 即springmvc次顶级web容器可以使用顶级web容器的bean装配自身bean

3)所以总结起来:springmvc的web应用加载了3种容器(能够理解到这一点,非常重要)

  • servlet容器;
    • 加载时读取web.xml配置文件;
  • springmvc顶级web容器; (所有二级web容器的父亲) ;
    • 加载时读取web.xml中contextConfigLocation属性指定的xml文件;如本文中的 applicationContext.xml , applicationContext-module1.xml ;
    • 初始化完成后,作为属性添加到servlet容器,属性名= org.springframework.web.context.WebApplicationContext.ROOT ;
  • springmvc二级web容器(次顶级web容器,把顶级web容器作为父亲); (servlet级别的容器,即每个servlet都可以拥有自身的容器)
    • 加载时读取web.xml中各servlet元素中contextConfigLocation属性指定的xml文件; 如本文中的dispatcher-servlet.xml , dispatcher-servlet2.xml , dispatcher-servlet3-upload.xml ;
    • 初始化完成后,作为属性添加到servlet容器,属性名= org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher;

4)目录结构:

在这里插入图片描述

5)web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app
        xmlns = "https://jakarta.ee/xml/ns/jakartaee"
        xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation = "https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
        version = "5.0"
        metadata-complete = "false"
>
  <display-name>springmvcDiscover</display-name>

  <!-- 指定ContextLoaderListener加载web容器时使用的多个xml配置文件(默认使用/WEB-INF/applicationContext.xml) -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml,/WEB-INF/applicationContext-module1.xml</param-value>
  </context-param>

  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- 注册过滤器代理 -->
  <filter>
    <filter-name>customFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>customFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- 配置监听器ContextLoaderListener,其加载顶层WebApplicationContext web容器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- 注册一级控制器 DispatcherServlet,用于拦截所有请求(匹配url-pattern) -->
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- DispatcherServlet启动读取xml配置文件加载组件,构建web容器(子),通过contextConfigLocation为其配置多个xml文件-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/dispatcher-servlet.xml,/WEB-INF/dispatcher-servlet2.xml,/WEB-INF/dispatcher-servlet3-upload.xml</param-value>
    </init-param>
    <load-on-startup>2</load-on-startup>
    <!-- 新增multipart-config 子元素,该servlet才启用文件上传功能(必须)  -->
    <multipart-config>
      <!-- 当上传文件被处理或文件超过fileSizeThreshold,文件的保存路径;默认为空串 -->
      <location>D:\temp\springmvcUploadDir</location>
      <!-- 上传文件字节最大值,若超过则抛出异常;默认无限;我们这里设置为20M -->
      <max-file-size>20971520</max-file-size>
      <!-- 请求报文字节最大值,若超过则抛出异常;默认无限;我们这里设置为1000M -->
      <max-request-size>1048576000</max-request-size>
      <!-- 临时保存到磁盘的文件大小最小值(超过该值就保存);默认0 -->
      <file-size-threshold>-1</file-size-threshold>
    </multipart-config>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

</web-app>


【1】DelegatingFilterProxy回顾

1)问题: 为什么要在web.xml中注册DelegatingFilterProxy ;

  • DelegatingFilterProxy的作用:作为Filter的代理对象;当对请求拦截时,把拦截逻辑委派给具体的Filter(如CustomFilter);
    • 物理结构上DelegatingFilterProxy在web.xml中注册,在servlet容器中;
    • 而 CustomFilter 在 applicationContext.xml 中注册,在springmvc顶级WebApplicationContext容器中;
  • 当然,我们讲,在web.xml中肯定可以注册CustomFilter来执行拦截逻辑;但无法装配spring容器的bean
  • 简单理解: 要把spring容器的bean装配到CustomFilter,则CustomerFilter必须注册到spring容器; 所以CustomerFilter在applicationContext.xml中注册(applicationContext.xml是springmvc顶级web容器加载的配置文件)
  • 又引入新问题:把CustomFilter注册到spring的顶级web容器中,servlet容器是无法识别的;由上文可知,filter是servlet级别的拦截,又servlet容器无法识别spring容器中的CustomFilter,所以如果没有中介,servlet容器是无法调用spring容器中的CustomFilter执行过滤逻辑
    • 解决方法: DelegatingFilterProxy 就是连接servlet容器与spring容器的中介;DelegatingFilterProxy在web.xml中配置,注册到servlet容器,servlet容器执行DelegatingFilterProxy的doFilter()方法,doFilter方法内部根据filter名称从spring容器中取出目标filter并执行目标filter的过滤逻辑;

2)详情参见 spring揭秘25-springmvc03-其他组件(文件上传+拦截器+处理器适配器+异常统一处理) 中【3.3】章节 ;

  • 这也由此引出了 Servlet容器与springmvc容器;


【1.1】DelegatingFilterProxy初始化过滤器bean

【DelegatingFilterProxy#initFilterBean()】 从springmvc容器中查找注册的过滤器bean

// 初始化过滤器bean
protected void initFilterBean() throws ServletException {
        synchronized(this.delegateMonitor) {
            if (this.delegate == null) {
                if (this.targetBeanName == null) {
                    // 获取目标过滤器名称(默认是DelegatingFilterProxy在web.xml中注册的名称)
                    this.targetBeanName = this.getFilterName(); 
                }
				// 获取spring的web容器,并通过web容器初始化过滤器(委派的过滤器)
                WebApplicationContext wac = this.findWebApplicationContext();
                if (wac != null) {
                    this.delegate = this.initDelegate(wac);
                }
            }
        }
    }
// 查找spring Web容器(顶级web容器)
protected WebApplicationContext findWebApplicationContext() {
        if (this.webApplicationContext != null) {
            WebApplicationContext var2 = this.webApplicationContext;
            if (var2 instanceof ConfigurableApplicationContext) {
                ConfigurableApplicationContext cac = (ConfigurableApplicationContext)var2;
                if (!cac.isActive()) {
                    cac.refresh();
                }
            }

            return this.webApplicationContext;
        } else { // 走else分支
            String attrName = this.getContextAttribute();
            return attrName != null ? WebApplicationContextUtils.getWebApplicationContext(this.getServletContext(), attrName) : WebApplicationContextUtils.findWebApplicationContext(this.getServletContext()); 
            // 走 WebApplicationContextUtils.findWebApplicationContext
        }
    }
// 

【WebApplicationContextUtils】 WebApplicationContextUtils.findWebApplicationContext() 查找web容器;

public static WebApplicationContext findWebApplicationContext(ServletContext sc) {
    // 通过ServletContext(servlet容器)获取springWeb容器
    WebApplicationContext wac = getWebApplicationContext(sc);
    if (wac == null) {
        Enumeration<String> attrNames = sc.getAttributeNames();

        while(attrNames.hasMoreElements()) {
            String attrName = (String)attrNames.nextElement();
            Object attrValue = sc.getAttribute(attrName);
            if (attrValue instanceof WebApplicationContext) {
                // spring的web容器实际作为ServletContext的属性值,被获取后,通过类型转换,得到WebApplicationContext类型的spring的web容器
                WebApplicationContext currentWac = (WebApplicationContext)attrValue;
                if (wac != null) { 
                    throw new IllegalStateException("No unique WebApplicationContext found: more than one DispatcherServlet registered with publishContext=true?");
                }

                wac = currentWac;
            }
        }
    }

    return wac;
}

// 通过ServletContext(servlet容器)获取springWeb容器
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    // WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = org.springframework.web.context.WebApplicationContext.ROOT
        return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    }
// 获取springWeb容器 
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
        Assert.notNull(sc, "ServletContext must not be null");
        // 根据属性名称(org.springframework.web.context.WebApplicationContext.ROOT) 从Servlet容器中获取SpringWeb容器 
        Object attr = sc.getAttribute(attrName);
        if (attr == null) {
            return null;
        } else if (attr instanceof RuntimeException) {
            RuntimeException runtimeException = (RuntimeException)attr;
            throw runtimeException;
        } else if (attr instanceof Error) {
            Error error = (Error)attr;
            throw error;
        } else if (attr instanceof Exception) {
            Exception exception = (Exception)attr;
            throw new IllegalStateException(exception);
        } else if (attr instanceof WebApplicationContext) {
            // 若名=org.springframework.web.context.WebApplicationContext.ROOT的属性值的类型是WebApplicationContext,则该属性值是spring的web容器 
            WebApplicationContext wac = (WebApplicationContext)attr;
            return wac;
        } else {
            throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
        }
    }

【WebApplicationContext】springmvc容器继承自 spring容器 ApplicationContext

public interface WebApplicationContext extends ApplicationContext {
    // org.springframework.web.context.WebApplicationContext.ROOT
    String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
    String SCOPE_REQUEST = "request";
    String SCOPE_SESSION = "session";
    String SCOPE_APPLICATION = "application";
    String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
    String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
    String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";

    @Nullable
    ServletContext getServletContext();
}


【2】从servlet容器获取springmvc顶级web容器

1)WebApplicationContextUtils.getWebApplicationContext()调试现场: 显然, servlet容器中存在名=org.springframework.web.context.WebApplicationContext.ROOT的属性值,这个属性值就是springmvc容器,类型是XmlWebApplicationContext

在这里插入图片描述


2)DelegatingFilterProxy获取springweb容器(或springmvc容器)后, 调用 initDelegate(WebApplicationContext wac) 方法,从springmvc容器中通过beanName获取具体Filter,并暂存到this.delegate;

  • 由运行时对象可知, springmvc容器类型是XmlWebApplicationContext , 其configLocations的值就是web.xml中配置的contextConfigLocation的值;该值被org.springframework.web.context.ContextLoaderListener读取并初始化springmvc容器(spring的web容器);

在这里插入图片描述



【2.1】从Servlet容器中获取springmvc容器总结

1)ServletContext抽象了servlet容器, WebApplicationContext抽象了springmvc容器;

  • 当然,本文通过web.xml部署web应用,所以springmvc容器的实际类型为XmlWebApplicationContext,它是WebApplicationContext的子类;

2)servlet容器如何与springmvc容器产生关联?

  • 由上文可知,springmvc容器是作为servlet容器的一个属性值而存在的(属性名为org.springframework.web.context.WebApplicationContext.ROOT)
  • 问题: 谁把springmvc容器作为一个属性添加到servlet容器? – ContextLoaderListener 上下文加载器监听器


【2.2】ContextLoaderListener加载springmvc顶级web容器并将其添加到servlet容器

1)ContextLoaderListener定义:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    // 上下文(容器)初始化
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}
  • ContextLoaderListener的全限定类名为org.springframework.web.context.ContextLoaderListener ,它是springmvc定义的,继承了springmvc的ContextLoader,且实现了servlet的ServletContextListener接口,以便servlet容器启动时调用ServletContextListener的contextInitialized() 初始化web容器;

  • 显然,servlet容器启动时调用ServletContextListener的contextInitialized() ,而ContextLoaderListener#contextInitialized()调用了ContextLoader#initWebApplicationContext()



【2.2.1】ContextLoader-springmvc上下文加载器

1)ContextLoader初始化springmvc容器

【web.xml】

<!-- 指定ContextLoaderListener加载web容器时使用的多个xml配置文件(默认使用/WEB-INF/applicationContext.xml) -->
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/applicationContext.xml,/WEB-INF/applicationContext-module1.xml</param-value>
</context-param>

【ContextLoader-initWebApplicationContext()】 初始化顶级web容器,其加载的配置文件是web.xml中contextConfigLocation元素配置的xml路径(/WEB-INF/applicationContext.xml,/WEB-INF/applicationContext-module1.xml);

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
    } else {
       // ...
        try {
            if (this.context == null) {
                // 这里仅仅是通过反射创建XmlWebApplicationContext实例(1个空容器),赋值给xontext
                this.context = this.createWebApplicationContext(servletContext);
            }

            WebApplicationContext var6 = this.context;
            if (var6 instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)var6;
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        ApplicationContext parent = this.loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
					// 真正初始化springmvc容器
                    this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            // springmvc容器初始化完成后,作为属性添加到servletContext(servlet容器)
            // WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE值等于org.springframework.web.context.WebApplicationContext.ROOT
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            } else if (ccl != null) {
                currentContextPerThread.put(ccl, this.context);
            }
           // ...
            return this.context;
        } catch (Error | RuntimeException var8) {
            logger.error("Context initialization failed", var8);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
            throw var8;
        }
    }
}

显然,通过上述代码【servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context)】可知,initWebApplicationContext()方法把springmvc容器作为ServletContext的属性添加到ServletContext



【ContextLoader-configureAndRefreshWebApplicationContext(WebApplicationContext, ServletContext)】配置与刷新web容器

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        String configLocationParam;
        // ...

        wac.setServletContext(sc);
    // 获取web.xml中配置的contextConfigLocation属性,属性值是xml文件路径,该xml用于加载springmvc容器 
        configLocationParam = sc.getInitParameter("contextConfigLocation");
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam); // 把springmvc容器需要加载的xml文件路径设置到springmvc容器本身
        }

        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment cwe) {
            cwe.initPropertySources(sc, (ServletConfig)null);
        }

        this.customizeContext(sc, wac);
    // refresh()方法才是真正初始化springmvc容器(注入bean,装配bean等,这里不展开)
        wac.refresh();
    }


【2.2.2】springmvc容器分类(springmvc顶级web容器与次顶级web容器)

1)springmvc顶级容器:通过ContextLoaderListener监听器初始化的springmvc容器是顶级web容器;

2)springmvc二级容器(次顶级容器): 各个Servlet(如DispatcherServlet)初始化的springmvc容器是二级容器或次顶级容器,springmvc二级容器把顶级容器作为父类,前者可以使用后者的bean用于装配自身bean ;



【3】DispatcherServlet初始化springmvc次顶级web容器(二级容器)

1)加载时读取web.xml中各servlet元素中contextConfigLocation属性指定的xml文件, 也即本文中的/WEB-INF/dispatcher-servlet.xml,/WEB-INF/dispatcher-servlet2.xml,/WEB-INF/dispatcher-servlet3-upload.xml;

【web.xml】 配置DispatcherServlet元素

<!-- 注册一级控制器 DispatcherServlet,用于拦截所有请求(匹配url-pattern) -->
<servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <!-- DispatcherServlet启动读取xml配置文件加载组件,构建web容器(子),通过contextConfigLocation为其配置多个xml文件-->
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/dispatcher-servlet.xml,/WEB-INF/dispatcher-servlet2.xml,/WEB-INF/dispatcher-servlet3-upload.xml</param-value>
  </init-param>
  <load-on-startup>2</load-on-startup>
  <!-- 新增multipart-config 子元素,该servlet才启用文件上传功能(必须)  -->
  <multipart-config>
    <!-- 当上传文件被处理或文件超过fileSizeThreshold,文件的保存路径;默认为空串 -->
    <location>D:\temp\springmvcUploadDir</location>
    <!-- 上传文件字节最大值,若超过则抛出异常;默认无限;我们这里设置为20M -->
    <max-file-size>20971520</max-file-size>
    <!-- 请求报文字节最大值,若超过则抛出异常;默认无限;我们这里设置为1000M -->
    <max-request-size>1048576000</max-request-size>
    <!-- 临时保存到磁盘的文件大小最小值(超过该值就保存);默认0 -->
    <file-size-threshold>-1</file-size-threshold>
  </multipart-config>
</servlet>
<servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>


【3.1】DispatcherServlet定义

1)DispatcherServlet继承FrameworkServlet ;

public class DispatcherServlet extends FrameworkServlet {
    //...
}

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

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

在这里插入图片描述

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

protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
    if (logger.isInfoEnabled()) {
       logger.info("Initializing Servlet '" + getServletName() + "'");
    }
    long startTime = System.currentTimeMillis();

    try {
        // 初始化web容器
       this.webApplicationContext = initWebApplicationContext();
       initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
       logger.error("Context initialization failed", ex);
       throw ex;
    }

    // ...
}

// 初始化web容器
protected WebApplicationContext initWebApplicationContext() {
    // rootContext 就是springmvc顶级web容器 
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    // wac才是DispatcherServlet加载的springmvc二级web容器 
		WebApplicationContext wac = null;    

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) {
				// The context has not yet been refreshed -> provide services such as
				// setting the parent context, setting the application context id, etc
				if (cwac.getParent() == null) {
					// The context instance was injected without an explicit parent -> set
					// the root application context (if any; may be null) as the parent
					cwac.setParent(rootContext);
				}
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
		if (wac == null) {
            // 若有现成的二级web容器,则直接获取 
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
            // 若没有现成的二级web容器,则创建二级web容器 
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			synchronized (this.onRefreshMonitor) {
                // 这里才是真正读取二级web容器对应的xml文件,并实例化对应spring bean的地方 
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
            // 把二级web容器作为servlet容器的属性设置到servlet容器 (这与springmvc顶级web容器类似,也是作为servlet容器的属性被引用)
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

【补充】FrameworkServlet初始化当前Servlet的web容器后,把该web容器作为属性添加到servlet容器

  • 如 DispatcherServlet对应的web容器,其属性名为 org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher

在这里插入图片描述



【3.1.1】创建二级web容器

1)首先获取springmvc顶级web容器, 然后判断是否有现成的该Servlet的二级web容器(DispatcherServlet),若有直接返回; 若没有,则调用createWebApplicationContext()方法创建二级web容器;

【FrameworkServlet#initWebApplicationContext()】

在这里插入图片描述
【FrameworkServlet#createWebApplicationContext(WebApplicationContext parent)】

  • 把rootContext(springmvc顶级web容器)传入createWebApplicationContext()方法,创建二级web容器
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
       throw new ApplicationContextException(
             "Fatal initialization error in servlet with name '" + getServletName() +
             "': custom WebApplicationContext class [" + contextClass.getName() +
             "] is not of type ConfigurableWebApplicationContext");
    }
    // 通过反射创建DispatcherServlet的web容器 
    ConfigurableWebApplicationContext wac =
          (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    // 获取该web容器的xml配置文件(spring容器xml配置文件)
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
       wac.setConfigLocation(configLocation);
    }
    // 根据configLocation指定的xml配置文件,初始化DispatcherServlet的web容器  
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

【代码解说】

configLocation就是web.xml中DispatcherServlet元素中configLocation嵌套元素指定的xml配置文件,如下;
在这里插入图片描述


【FrameworkServlet#configureAndRefreshWebApplicationContext()】根据configLocation指定的xml配置文件,初始化DispatcherServlet的web容器
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
       // The application context id is still set to its original default value
       // -> assign a more useful id based on available information
       if (this.contextId != null) {
          wac.setId(this.contextId);
       }
       else {
          // Generate default id...
          wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
       }
    }

    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    // The wac environment's #initPropertySources will be called in any case when the context
    // is refreshed; do it eagerly here to ensure servlet property sources are in place for
    // use in any post-processing or initialization that occurs below prior to #refresh
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment cwe) {
       cwe.initPropertySources(getServletContext(), getServletConfig());
    }

    postProcessWebApplicationContext(wac);
    applyInitializers(wac); // 应用初始化器 
    wac.refresh(); // 加载xml配置文件,实例化bean并正常到当前容器  
}


【3.1.2】应用初始化器到springmvc二级容器

【FrameworkServlet#applyInitializers()】

protected void applyInitializers(ConfigurableApplicationContext wac) {
    String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
    if (globalClassNames != null) {
       for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
          this.contextInitializers.add(loadInitializer(className, wac));
       }
    }

    if (this.contextInitializerClasses != null) {
       for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
          this.contextInitializers.add(loadInitializer(className, wac));
       }
    }

    AnnotationAwareOrderComparator.sort(this.contextInitializers);
    for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
       initializer.initialize(wac); // 调用初始化器
    }
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2188040.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot+Vue的科研课题项目管理系统源码

文章目录 1.技术架构2.主要功能3.获取方式 1.技术架构 后端&#xff1a;SpringBoot 前端&#xff1a;Vue – Element UI 2.主要功能 登录 /注销、 用户管理、项目管理、申报管理、变更管理、 结题管理、角色管理、权限管理、数据字典等功能 3.获取方式 点击下方名片&a…

android 全面屏最底部栏沉浸式

Activity的onCreate方法中添加 this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); Android 系统 Bar 沉浸式完美兼容方案自 Android 5.0 版本&#xff0c;Android 带来了沉浸式系统 ba - 掘金 (juejin.cn)https://juejin.cn/post/7075578…

pycharm中使用anaconda创建多环境,无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称

问题描述 用的IDE是&#xff1a; 使用anaconda创建了一个Python 3.9的环境 结果使用pip命令的时候&#xff0c;报错 无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称 解决方案 为了不再增加系统变量&#xff0c;我们直接将变量添加在当前项目中你的Ter…

Python3使用cv_bridge转换ROS的image信息

0. Preface 现在很多新的图片处理model都是基于python3的&#xff0c;而ROS还是2.7的&#xff0c;要结合起来用不可避免有很多问题&#xff0c;以下以接收ROS image为例子 以下方法需要用的anaconda&#xff0c;安装方法有很多blog分享 1. Preparation 以下是python3接收ima…

【黑马点评】0.环境配置--Redis6.2.6和可视化工具在Windows上的安装

黑马点评--0.Redis6.2.6在windows上的环境配置与可视化 0 前言1 下载安装2 解压后运行msi文件3 修改配置文件并打开Redis3.1 修改密码&#xff08;可选&#xff09;3.2 测试 4 Redis可视化&#xff08;可选&#xff09;4.1 Another Redis Desktop Manager下载安装4.2 连接Redis…

删除苹果手机所有照片的方法有哪些?

在这个数码摄影盛行的时代&#xff0c;手机相册里可能堆积了成千上万的照片&#xff0c;尤其是苹果手机用户。无论是为了释放存储空间&#xff0c;还是因为想要重新开始整理照片&#xff0c;删除所有照片可能成为一个必要的任务。那么&#xff0c;如何有效地删除删除苹果手机所…

防止老年痴呆的一题

如何只移动三个圆使下图变为正三角? 真相如此简单:

Redis常用命令(超详细整理)

Redis常用命令&#xff08;超详细整理&#xff09; 服务器相关命令 ping &#xff1a; 检测连接是否存活echo&#xff1a; 在命令行打印一些内容quit、exit&#xff1a; 退出客户端shutdown&#xff1a; 退出服务器端info&#xff1a; 返回redis相关信息config get dir/* 实时…

69.【C语言】动态内存管理(重点)(2)

目录 3.free函数 cplusplus网的翻译 提炼要点 使用 x86debug环境下, 打开内存窗口 建议 3.free函数 cplusplus的介绍 点我跳转 cplusplus网的翻译 函数 free void free (void* ptr); 释放内存块 之前通过调用malloc来分配一块内存,calloc和recalloc是来释放内存块的,让内…

Sublime快捷键的使用和修改

sublime快捷键 Ctrl Shift D 复制光标所在整行&#xff0c;插入到下一行Ctrl Shift K 删除整行 如果快捷键冲突了&#xff0c;就需要修改 sublime快捷键修改 示例&#xff1a;当前 Ctrl Shift D 冲突了 1.选择 首选项 -> 按键绑定-默认 2.按住 Ctrl F&#xff0…

初级前端面试常见问题(上岸某公司)

一、HTML5CSS篇 1.css隐藏元素方法 display:none position:absolute 移除到可视区域之外 visibility:hidden z-index:负值&#xff0c;用其他元素遮盖 opacity:0 clip/clip-…

心觉:做真正对自己成长有价值的事情

Hi&#xff0c;我是心觉&#xff0c;与你一起玩转潜意识、脑波音乐和吸引力法则&#xff0c;轻松掌控自己的人生&#xff01; 挑战每日一省写作189/1000天 不知不觉已经持续写了189天&#xff0c;每日一篇从不间断 这段时间最大的收获&#xff0c;就是看透了很多事物 自我觉…

Typora解决图片复制到其他博客平台,解决图片显示转存失败(CSDN除外)

目录 一、Typora这个Markdown编辑器的确好用1.1 安装 二、 问题“图片转存失败”2.1 问题具体显示如下&#xff1a;2.2 问题分析&#xff1a;其实就是图片在typora里面是使用的本地路径&#xff0c;因此不显示&#xff0c; 三、解决方案3.1打开Typora&#xff0c;按下述图片显示…

51c视觉~CV~合集2

我自己的原文哦~ https://blog.51cto.com/whaosoft/11638046 一、 搭建半自动标注工具 本文主要介绍的半自动标注工具为pyOpenAnnotate&#xff0c;此工具是基于Python和OpenCV实现&#xff0c;最新版本为0.4.0&#xff0c;可通过下面指令安装使用&#xff1a; pip install p…

【开源免费】基于SpringBoot+Vue.JS洗衣店订单管理系统(JAVA毕业设计)

本文项目编号 T 068 &#xff0c;文末自助获取源码 \color{red}{T068&#xff0c;文末自助获取源码} T068&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 顾…

智慧水务可视化:高效管理水资源

利用图扑先进的可视化技术&#xff0c;实现对水资源的实时监控与高效管理&#xff0c;提高水务工作的透明度和决策效率&#xff0c;促进水资源的可持续利用。

通信工程学习:什么是ARP地址解析协议

ARP&#xff1a;地址解析协议 ARP&#xff08;Address Resolution Protocol&#xff0c;地址解析协议&#xff09;是一种在计算机网络中用于将IP地址映射为MAC地址的协议。它是TCP/IP协议栈中的一个重要组成部分&#xff0c;特别是在IPv4网络中&#xff0c;扮演着至关重要的角色…

二叉树深度学习——二叉树的最近公共祖先

1.题目解析 题目来源&#xff1a;236.二叉树的最近公共祖先 测试用例 2.算法原理 基本解法O(N^2) 这里我们可以将寻找的两节点分为在同一子树与不在同一子树的两种情况分析 1.在同一子树&#xff1a;由于自己可以是自己的祖先&#xff0c;所以寻找层数较高的节点就是二者的公共…

AFSim仿真系统 --- 系统简解_03( Warlock模块 - 人工干预 在仿真领域中指的是AFSIM的操作员互动可视化应用程序)

我们就用保留单词 Warlock Warlock&#xff08;在仿真领域中指的是AFSIM的操作员互动可视化应用程序--人工干预 &#xff09; Warlock是AFSIM的操作员环环相扣&#xff08;Operator-in-the-Loop&#xff09;视觉应用程序。它提供了一个图形环境&#xff0c;用于在运行时查看和…