欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

Spring 揭示 25-Springmvc04-Servlet 容器和 Springmvc 容器摘要

最编程 2024-10-04 07:26:16
...

文章目录

  • 【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揭秘》,作者王福强,非常棒的一本书,墙裂推荐;

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 
						

上一篇: 更新 - Python 操作系统

下一篇: 系统架构师 - 论文题目(2022 年下半年)

推荐阅读