当前位置:   article > 正文

SpringMVC源码解析系列(1)之Spring MVC的初始化流程_springmvc的applicationcontext 如何在servlet前初始化

springmvc的applicationcontext 如何在servlet前初始化

Spring MVC的初始化流程

重要的前置知识: ServletContext

1.创建时间

在WEB容器(tomcat)启动时,他会为每个WEB应用程序创建一个对应的ServletContext对象,他代表当前的web应用,又名application,对于一个web项目来说,只有全局唯一的一个ServletContext对象

2.获取方式:

①从当前Servlet对象中获得

ServletContext c1=this.getServletContext()

②从ServletConfig对象中获得

ServletContext c2=this.getServletConfig().getServletContext()

③从会话对象中获得

ServletContext c3=req.getSession().getServletContext()

④从HttpServletRequest请求对象中获得

ServletContext c4 = req.getServletContext()

3.用途:

①获取全局配置文件的参数(全局配置文件参数会保存在servletContext中)

  1.  <!-- 全局配置参数 -->
  2. <context-param>  
  3. <param-name>listener1</param-name>    
  4.    <param-value>WEB-INF/classes/listener.txt</param-value>  
  5. </context-param>
String path = c1.getInitParameter("listener1");

②获取web项目的绝对路径

String realPath = servletContext.getRealPath(path);

③用于获取WebRoot文件夹下的文件(.JSP,.PNG,.html,.doc,)资源

 c1.getResourceAsStream("index.jsp");

当一个 Web 应用部署到容器内时(例如 Tomcat),在 Web 应用开

始响应执行用户请求前,以下步骤会被依次执行:

  • 部署描述文件中(例如 Tomcat 的web.xml)由<listener>元素标记的事件监听器会被创建和初始化

  • 对于所有事件监听器,如果实现了ServletContextListener接口,将会执行其实现的contextInitialized()方法

  • 部署描述文件中由<filter>元素标记的过滤器会被创建和初始化,并调用其init()方法

  • 部署描述文件中由<servlet>元素标记的 Servlet 会根据<load-on-startup>的权值按顺序创建和初始化,并调用其init()方法

web应用部署初始化执行流程如下:(图片来自网图)

在 Tomcat 下 Web 应用的初始化流程是,先初始化listener接着初始化filter最后初始化servlet

接下来,以一个常见的tomcat的web.xml配置文件为例,进行Spring MVC的启动过程分析:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  3.         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  5.         version="4.0">
  6.    
  7.      <!--全局变量配置-->
  8.  <context-param>
  9.    <param-name>contextConfigLocation</param-name>
  10.      <!-- 此处配置文件中的bean是放在根ioc容器中的 -->
  11.    <param-value>classpath:applicationContext-*.xml</param-value>
  12.  </context-param>
  13.    <!-- 配置上下文加载监听器 -->
  14.    <listener>
  15.        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  16.    </listener>
  17.    <!-- 配置编码过滤器 -->
  18.    <filter>
  19.        <filter-name>CharacterEncodingFilter</filter-name>
  20.        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  21.        <init-param>
  22.            <param-name>encoding</param-name>
  23.            <param-value>UTF-8</param-value>
  24.        </init-param>
  25.        <init-param>
  26.            <param-name>forceResponseEncoding</param-name>
  27.            <param-value>true</param-value>
  28.        </init-param>
  29.    </filter>
  30.    <filter-mapping>
  31.        <filter-name>CharacterEncodingFilter</filter-name>
  32.        <url-pattern>/*</url-pattern>
  33.    </filter-mapping>
  34.    <!-- 配置前端控制器 -->
  35.    <servlet>
  36.        <servlet-name>DispatcherServlet</servlet-name>
  37.        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  38.        <init-param>
  39.            <param-name>contextConfigLocation</param-name>
  40.            <!-- 此处配置文件中的bean是放在子ioc容器(当前servlet所关联的ioc容器)中的 -->
  41.            <param-value>classpath:springMVC.xml</param-value>
  42.        </init-param>
  43.    </servlet>
  44.    <servlet-mapping>
  45.        <servlet-name>DispatcherServlet</servlet-name>
  46.        <url-pattern>/</url-pattern>
  47.    </servlet-mapping>
  48. </web-app>

1、因为我们定义了<context-param>标签,用于配置全局变量,所以tomcat会先去读取标签中的内容,放入之前创建好的servletcontext(application)中,作为Web应用的全局变量使用,然后再进行上述的初始化启动过程

Listener的初始化过程

2、tomcat会解析<listener>标签,创建相应的Listener对象,如果它实现了ServletContextListener接口,就会调用它的contextInitialized方法。

ServletContextListener接口

该接口只有两个方法contextInitializedcontextDestroyed,这里采用的是观察者模式,也称为为”订阅-发布“模式,实现了该接口的listener会向发布者进行订阅,当 Web 应用初始化或销毁时会分别调用上述两个方法。

  1. public interface ServletContextListener extends EventListener {
  2.    // servletContext初始化的时候被调用(tomcat启动)
  3.    void contextInitialized(ServletContextEvent var1);
  4. // servletContext销毁的时候被调用(tomcat关闭)
  5.    void contextDestroyed(ServletContextEvent var1);
  6. }

ContextLoaderListener

  1. public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
  2.    public ContextLoaderListener() {
  3.   }
  4.    public ContextLoaderListener(WebApplicationContext context) {
  5.        super(context);
  6.   }
  7. // 容器初始化时被调用
  8.    public void contextInitialized(ServletContextEvent event) {
  9.        // 初始化web的应用上下文
  10.        this.initWebApplicationContext(event.getServletContext());
  11.   }
  12.    public void contextDestroyed(ServletContextEvent event) {
  13.        this.closeWebApplicationContext(event.getServletContext());
  14.        ContextCleanupListener.cleanupAttributes(event.getServletContext());
  15.   }
  16. }

ContextLoader#initWebApplicationContext

  1. public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
  2.        /*
  3.    首先通过WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
  4.    (WebApplicationContext.class.getName() + ".ROOT")
  5.    这个String类型的静态变量获取一个根IoC容器,根IoC容器作为全局变量
  6.    存储在application对象中,如果存在则有且只能有一个
  7.    如果在初始化根WebApplicationContext即根IoC容器时发现已经存在
  8.    则直接抛出异常,因此web.xml中只允许存在一个ContextLoader类或其子类的对象
  9.    */
  10.    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
  11.        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!");
  12.   } else {
  13.        servletContext.log("Initializing Spring root WebApplicationContext");
  14.        Log logger = LogFactory.getLog(ContextLoader.class);
  15.        if (logger.isInfoEnabled()) {
  16.            logger.info("Root WebApplicationContext: initialization started");
  17.       }
  18.        long startTime = System.currentTimeMillis();
  19.        try {
  20.            // 如果当前webapplicationcontext不存在就创建一个根webapplication
  21.            if (this.context == null) {
  22.                this.context = this.createWebApplicationContext(servletContext);
  23.           }
  24.            if (this.context instanceof ConfigurableWebApplicationContext) {
  25.                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
  26.                if (!cwac.isActive()) {
  27.                    if (cwac.getParent() == null) {
  28.             // 此处会默认返回null
  29.                        ApplicationContext parent = this.loadParentContext(servletContext);
  30.              // 设置当前根容器的父容器为null,表明当前容器为根容器
  31.                        cwac.setParent(parent);
  32.                   }
  33. // 配置并刷新根webApplicationContext,在这里会进行bean的创建和初始化
  34.                    this.configureAndRefreshWebApplicationContext(cwac, servletContext);
  35.               }
  36.           }
  37. // 如果在进行容器刷新的时候没有抛出异常,会继续来到此处:将当前的根webApplicationcontext设置到servletContext中          
  38.            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
  39.            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
  40.            if (ccl == ContextLoader.class.getClassLoader()) {
  41.                currentContext = this.context;
  42.           } else if (ccl != null) {
  43.                currentContextPerThread.put(ccl, this.context);
  44.           }
  45.            if (logger.isInfoEnabled()) {
  46.                long elapsedTime = System.currentTimeMillis() - startTime;
  47.                logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
  48.           }
  49. // 至此根webApplicationContext创建完成
  50.            return this.context;
  51.       } catch (Error | RuntimeException var8) {
  52.            logger.error("Context initialization failed", var8);
  53.            // 如果在刷新容器的过程中抛出异常(没有读取到我们的配置文件),会来到此处,将根webapplicationContext设置到servletContext中
  54.            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
  55.            throw var8;
  56.       }
  57.   }
  58. }

ContextLoader#createWebApplicationContext

下面是ApplicationContext的继承关系图:

  1. protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
  2.    // 决策webapplicationcontext的真实类型
  3.    Class<?> contextClass = this.determineContextClass(sc);
  4.    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
  5.        throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
  6.   } else {
  7.        // 创建了一个ConfigurableWebApplicationContext类型的根web应用上下文
  8.        return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
  9.   }
  10. }
  1. protected Class<?> determineContextClass(ServletContext servletContext) {
  2.    // 获取名称为contextClass的全局初始化参数,就是查看我们有没有自定义web应用上下文
  3.        String contextClassName = servletContext.getInitParameter("contextClass");
  4.        if (contextClassName != null) {
  5.            try {
  6.        // 如果我们自定义了应用上下文,就将加载对应的类,返回它的类对象
  7.                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
  8.           } catch (ClassNotFoundException var4) {
  9.                throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
  10.           }
  11.       } else {
  12.            // 如果没有自己定义,就去默认的策略配置中获取,此时获取到的是类型是XMLWebApplicationContext,
  13.            /**
  14.             ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
  15.            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
  16.            **/
  17.            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
  18.            try {
  19.                // 加载类,返回类对象(XMLWebApplicationContext)
  20.                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
  21.           } catch (ClassNotFoundException var5) {
  22.                throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
  23.           }
  24.       }
  25.   }

defaultStrategies的结构:

ContextLoader#configureAndRefreshWebApplicationContext

  1. protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
  2.    String configLocationParam;
  3.    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
  4.       // 给webapplicationcontext设置id,先去全局初始化参数中找,没有配置,就设置id为”类名:项目访问代表路径“,eg:org.springframework.web.context.WebApplicationContext:/springmvc_05_war_exploded
  5.        configLocationParam = sc.getInitParameter("contextId");
  6.        if (configLocationParam != null) {
  7.            wac.setId(configLocationParam);
  8.       } else {
  9.            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
  10.       }
  11.   }
  12. // 向根web应用上下文中设置servletContext
  13.    wac.setServletContext(sc);
  14.    // 获取我们配置的全局初始化参数中的key=contextConfigLocation所对应的value
  15.    // 也就是获取springmvc的配置文件的名称(路径)
  16.    configLocationParam = sc.getInitParameter("contextConfigLocation");
  17.    if (configLocationParam != null) {
  18.        // 如果配置了,将其设置到webApplicationContext的配置文件路径数组中configLocations,如果没有配置会使用默认的配置文件路径/WEB-INF/applicationContext.xml
  19.        wac.setConfigLocation(configLocationParam);
  20.   }
  21.    ConfigurableEnvironment env = wac.getEnvironment();
  22.    if (env instanceof ConfigurableWebEnvironment) {
  23.       ((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
  24.   }
  25.    this.customizeContext(sc, wac);
  26.    // 刷新容器,调用Spring中的AbstractApplicationContext的refresh()方法(前面分析过注解版的,现在分析xml文件版的)
  27.    // 1.创建bean工厂
  28.    // 2.加载配置文件
  29.    // 3.进行包扫描,将扫描到的组件注册到bean工厂中--->注册的是beanDefinition
  30.    // 4.注册配置文件中的bean--->注册的是beanDefinition
  31.    // 5.接下来就与注解配置一样了(调用bean工厂的后置处理器(扫描包、注册bean)、注册bean后置处理器、创建bean)
  32.    // 但是,如果我们没有配置contextConfigLocation全局初始化参数,在默认路径下(/WEB-INF/applicationContext.xml)也没有相应的配置文件,创建完bean工厂后会抛出异常
  33.    wac.refresh();
  34. }

整个ContextLoaderListener类的启动过程到此就结束了,可以发现,创建ContextLoaderListener是比较核心的一个步骤,主要工作就是为了创建根IOC 容器并使用特定的key将其放入到application对象中,供整个 Web 应用使用,在ContextLoaderListener类中构造的根IOC 容器配置的 Bean 是全局共享的。

Filter的初始化

3.tomcat会解析<filter>标签,创建<filter-class>标签中指定的类对象,设置属性。

Servlet的初始化

Web 应用启动的最后一个步骤就是创建和初始化相关 Servlet,在开发中常用的 Servlet 就是DispatcherServlet前端控制器,前端控制器作为中央控制器是整个 Web 应用的核心,用于获取分发用户请求并返回响应

通过类图可以看出DispatcherServlet类的间接父类实现了Servlet接口,因此其本质上依旧是一个ServletDispatcherServlet类的设计很巧妙,上层父类不同程度的实现了相关接口的部分方法,并留出了相关方法用于子类覆盖,将不变的部分统一实现,将变化的部分预留方法用于子类实现

DispatcherServlet类的初始化过程将模板方法使用的淋漓尽致,其父类完成不同的统一的工作,并预留出相关方法用于子类覆盖去完成不同的可变工作。

DispatcherServelt类的本质是Servlet,通过文章开始的讲解可知,在 Web 应用部署到容器后进行Servlet初始化时会调用相关的init(ServletConfig)方法,因此,DispatchServlet类的初始化过程也由该方法开始。先看一下整体的调用流程图:

主要的逻辑实现从FrameworkServletinitWebApplicationContext()方法开始

FrameworkServlet#initWebApplicationContext()

  1. protected WebApplicationContext initWebApplicationContext() {
  2.    // 从servletContext中获取根webApplicationContext,由于我们在初始化监听器的时候创建了根webApplicationContext,所以可以获取到,如果我们没有配置监听器,就不会创建根WebApplicationContext
  3.    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
  4.    WebApplicationContext wac = null;
  5.    // 当前servlet的webApplicationContext=null,此时我们还没有给Servlet的webApplicationContext属性赋值,如果配置了监听器,创建的根webApplicationContext也只在servletContext中,被全局共享
  6.    if (this.webApplicationContext != null) {
  7.        wac = this.webApplicationContext;
  8.        if (wac instanceof ConfigurableWebApplicationContext) {
  9.            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
  10.            if (!cwac.isActive()) {
  11.                if (cwac.getParent() == null) {
  12.                    cwac.setParent(rootContext);
  13.               }
  14.                this.configureAndRefreshWebApplicationContext(cwac);
  15.           }
  16.       }
  17.   }
  18. // 此时,因为我们还没有给当前servlet设置属性,所偶一也找不到WebApplicationContext
  19.    if (wac == null) {
  20.        wac = this.findWebApplicationContext();
  21.   }
  22.    if (wac == null) {
  23.        // 创建webApplicationContext,此处的创建逻辑和之前分析过的ContextLoader中的创建逻辑大体相同
  24.        /**
  25.        1.获取webApplicationContext类型对象
  26.        2.创建相对应的实例对象
  27.        3.将传进去的rootContext设置为它的父容器
  28.        4.设置配置文件的路径
  29.        5.配置并刷新容器(读取springMVC.xml配置文件中的bean将其注册到工厂中,创建bean实例...)  
  30.        **/
  31.        wac = this.createWebApplicationContext(rootContext);
  32.   }
  33.    if (!this.refreshEventReceived) {
  34.        synchronized(this.onRefreshMonitor) {
  35.            this.onRefresh(wac);
  36.       }
  37.   }
  38.    if (this.publishContext) {
  39.        String attrName = this.getServletContextAttributeName();
  40.        this.getServletContext().setAttribute(attrName, wac);
  41.   }
  42. // 至此
  43.    return wac;
  44. }

从上面createWebApplicationContext(rootContext)中可以看出,我们给当前的servlet创建了一个IOC容器,并且给它设置父容器,那为什么明明都有一个父容器了,还要创建子容器呢?

父子容器类似于类的继承关系,子类可以访问父类中的成员变量,而父类不可访问子类的成员变量,同样的,子容器可以访问父容器中定义的 Bean,但父容器无法访问子容器定义的 Bean

根 IoC 容器做为全局共享的 IoC 容器放入 Web 应用需要共享的 Bean,而子 IoC 容器根据需求的不同,放入不同的 Bean,这样能够做到隔离,保证系统的安全性。

当 IoC 子容器构造完成后调用了onRefresh()方法,该方法的调用与initServletBean()方法的调用相同,由父类调用但具体实现由子类覆盖,调用onRefresh()方法时将前文创建的 IoC 子容器作为参数传入,查看DispatcherServletBean类的onRefresh()方法源码如下:

  1. protected void onRefresh(ApplicationContext context) {
  2.    this.initStrategies(context);
  3. }
  4. // 以下的初始化操作,就是从ioc容器中获取创建好的bean,然后将其设置为DispatcherServlet的属性值
  5. protected void initStrategies(ApplicationContext context) {
  6.    this.initMultipartResolver(context);
  7.    this.initLocaleResolver(context);
  8.    this.initThemeResolver(context);
  9.    // 初始化处理器映射器,会先从IOC容器中获取,获取不到去找默认的
  10.    this.initHandlerMappings(context);
  11.    // 初始化处理器适配器,会先从IOC容器中获取,获取不到去找默认的
  12.    this.initHandlerAdapters(context);
  13.    this.initHandlerExceptionResolvers(context);
  14.    this.initRequestToViewNameTranslator(context);
  15.    this.initViewResolvers(context);
  16.    this.initFlashMapManager(context);
  17. }

总结:Spring MVC的初始化流程

1.tomcat容器启动时,会去读取web项目下的web.xml文件

2.解析<context-param>标签中的参数

3.创建一个全局共享对象ServletContext,也叫application,将全局共享的参数放入ServletContext中

4.解析<Listener>标签,实例化Listener监听器对象,一般会使用ContextLoaderListener类,如果使用了ContextLoaderListener类,就会创建一个WebApplicationContext对象,这个对象就是根IOC容器,它会去读取<context-param>标签中设置的key为contextConfigLocationd所对应的value,作为要加载的配置文件,创建bean工厂,读取配置文件,创建bean实例,将根IOC容器放入ServletContext中,但是这个根IOC容器只能访问到自己容器中的bean实例,无法访问其子容器中的bean实例

5.解析<filter>标签,创建filter对象

6.解析<Servlet>标签,创建servlet对象,这个servlet通常指前端控制器DispatcherServlet,它间接实现了Servlet接口,所以会调用Servlet接口的init(ServletConfig config)方法,但是DispatcherServlet没有重写此方法,所以会调用它的父类GenericServlet重写的Servlet接口的init()方法,在重写父类的init()方法中又会调用自己的init()方法,但是自己的init()方法是个空方法,所以最终会调用它子类HttpServletBean重写它的init()方法,这样在父类中调用子类重写父类的空方法,最终会调用到FrameWorkServlet的initWenApplicationContext的方法,在此方法中创建一个当前Servlet对相应的子IOC容器,将全局IOC容器设置为它的父容器,读取<init-param>配置的xml文件并加载相关 Bean。

7.调用DispatcherServlet的onRefresh()方法,刷新容器,初始化属性,就是获取容器中的bean设置为自己的属性。

如有问题欢迎指正.....

参考文章:详述 Spring MVC 启动流程及相关源码分析_CG国斌的博客-CSDN博客_springmvc启动流程

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/1015729
推荐阅读
相关标签
  

闽ICP备14008679号