【Spring杂烩】探讨Spring项目ApplicationContext的初始化过程

探讨Spring项目ApplicationContext的初始化过程


  • Spring Xml 驱动 | Spring容器的初始化流程
    • 配置
    • 父子容器的介绍
    • 父容器初始化
    • 子容器初始化
    • 小结
  • Spring Annotation 驱动 | Spring容器的初始化流程
  • 参考资料

前提说明


  • 其实我目前使用更多的是Spring Boot, 而非原生的Spring项目;SpringBootSpring项目加载ApplicationContext的流程其实是不太一样的,那么这里为什么会写这篇文章了,目的是了解历史,才能更好的了解未来

  • Xml驱动的Spring项目和Annotation驱动的Spring项目,加载ApplicationContext的流程其实是一样的,只不过加载的ApplicationContext类型不同以及触发加载的方式不同,所以注解的方式就偷懒了


Spring Xml 驱动 | Spring容器的初始化流程


配置

在XML开发的时期,我们通常要在web.xml中配置ApplicationContext,如下
(web.xml 是J2EE时代Servlet容器的规范文件,将容器的配置进入其中)


  <context-param>
    <param-name>contextConfigLocationparam-name>
    <param-value>classpath:applicationContext-*.xmlparam-value>
  context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
  listener>

  
  <servlet>
    <servlet-name>springmvcservlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    <init-param>
      <param-name>contextConfigLocationparam-name>
      <param-value>classpath:springmvc.xmlparam-value>
    init-param>
     
    <load-on-startup>1load-on-startup>
  servlet>

父子容器的介绍

传统Spring项目中,存在两个容器,即SpringMVC父子容器:

  • 根容器:WebApplicationContext(Spring容器)
  • 子容器:SpringMVC容器

通过上面的xml配置,我们可以知道根容器是通过org.springframework.web.context.ContextLoaderListener进行初始化
SpringMVC子容器是通过DisPatcherServlet的某个方法进行初始化。这两个父子容器是所存储的数据是不相同的

【Spring杂烩】探讨Spring项目ApplicationContext的初始化过程_第1张图片
@图片来源于Spring和SpringMVC父子的容器之道—[上篇]

一般建议两个容器分别存储不同定位的Bean:

  • Spring容器存储Dao层,Service层的以及非Web层的Bean
  • SpringMVC容器存储Controller层以及实现Web层的基础Bean

两个容器之间的特点:

  • 父容器是无法访问子容器的Bean
  • 子容器可以访问父容器的Bean

父容器初始化

【Spring杂烩】探讨Spring项目ApplicationContext的初始化过程_第2张图片
@流程图来源于深入理解 spring 容器,源码分析加载过程

以下是ContextLoaderListener的初始化根WebApplicationContext容器的方法,从这里我们可以看到,初始化根WebApplicationContext容器需要先拿到Servelt容器

	/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		//将获得Servlet容器传入initWebApplicationContext方法中
		initWebApplicationContext(event.getServletContext());
	}



流程:

  • 启动项目时触发ContextLoaderListener#contextInitialized()方法,该方法就做一件事:
    1. 传入ServletContext,通过父类contextLoader的initWebApplicationContext方法创建WebApplicationContext
  • ContextLoader#initWebApplicationContext方法做了五件事:
    1. 通过createWebApplicationContext方法得到XmlWebApplicationContext空实例;
    2. 为空XmlWebApplicationContext实例配置部分属性(如父容器,没有则为null)
    3. 调用configureAndRefreshWebApplicationContext方法初始化剩余容器属性、初始化Bean、刷新容器
    4. 将XmlWebApplicationContext放入ServletContext(就是Java Web的全局变量)中
    5. 返回XmlWebApplicationContext实例
  • ContextLoader#createWebApplicationContext做了两件事情:
    1. 通过 determineContextClass方法判断要构造的上下文类型,默认是XmlWebApplicationContext
    2. 通过instantiateClass实例化XmlWebApplicationContext对象(相当于new 无参构造)
  • ContextLoader#configureAndRefreshWebApplicationContext做了三件事情:
    1. 为XmlWebApplicationContext实例配置部分属性(如父容器,容器id,插入ServletContext,configLocationParam)
    2. 它会从web.xml中读取名为contextConfigLocation的配置,这就是spring xml数据源设置,然后放到ApplicationContext中,最后调用refresh方法刷新容器
    3. 完成ApplicationContext创建之后就是将其放入ServletContext中,注意它存储的key值常量

通过以上的流程,我们的父容器就被初始化完毕了~


子容器初始化

SpringMVC容器的初始化则与web.xml配置的DispatchServlet密切相关了。

DispatcherServlet是前端控制器设计模式的实现,提供Spring MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处。

DispatcherServlet的作用:

  • Web层的Url映射分派
  • Spring MVC容器初始化

DispatcherServlet的继承关系图:
【Spring杂烩】探讨Spring项目ApplicationContext的初始化过程_第3张图片

流程:

【Spring杂烩】探讨Spring项目ApplicationContext的初始化过程_第4张图片

@流程图来源于深入理解 spring 容器,源码分析加载过程

要注意的是,当你需要调试时,需要在web.xml文件中配置了1,这则代表DispatcherServlet的初始化跟随项目的启动

  • HttpServletBean#init()

    1. 方法中执行initServletBean方法进行初始化操作,实际调用的是子类(FrameworkServlet)重写后的initServletBean()方法
  • FrameworkServlet#initServletBean()

    1. 重写了initServletBean方法,调用initWebApplicationContext()执行子容器的初始化。
  • FrameworkServlet#initWebApplicationContext

    1. 通过WebApplicationContextUtils.getWebApplicationContext(getServletContext())获取父容器
    2. 通过FrameworkServlet#createWebApplicationContext(rootContext)方法传入父容器去创建子容器
    3. 获取初始化后的子容器,返回实例
  • FrameworkSerlvet#createWebApplicationContext

    1. 通过 BeanUtils.instantiateClass(contextClass)实例化空XmlWebApplicationContext实例作为初始子容器
    2. 初始化子容器的基本属性(Environment,Parent,ConfigLocation)
    3. 调用FrameworkServlet#configureAndRefreshWebApplicationContext()去初始Bean以及刷新容器
  • FrameworkSerlvet#configureAndRefreshWebApplicationContext()

    1. 初始化子容器部分属性(contextId,插入ServletContext,NameSpace)
    2. 为子容器添加容器刷新监听事件(SourceFilteringListener)
    3. 调用FrameworkSerlvet#applyInitializers()方法对子容器进行委托
    4. 刷新容器,完成初始化
  • 无论是自己创建还是获取现有的WebApplicationContext,最终都会让传入子容器执行configureAndRefreshWebApplicationContext()方法进行子容器初始化。

经过以上流程,子容器就会初始化完毕啦~


小结

  • 父容器初始化只跟ContextLoaderListener和其父类ContextLoader有关
    通过ContextLoaderListener监听启动,后续实际调用爸爸的方法

  • 子容器初始化只跟DispatcherServlet和其爷爷HttpServletBean以及他爸爸FrameworkSerlvet类有关
    通过初始化DispatcherServlet从而触发爷爷的init方法,再实际调用爸爸的初始化方法

  • 即使是注解方式的Spring项目,只要配置了ContextLoaderListener和DispatcherServlet,那么他们的容器的初始化流程都是一样的。只不过容器类型不再是XmlWebApplicationContext,而是AnnotationWebApplicationContext

  • 其实在Spring项目不复杂的情况下,我们多半可以不需要配置根容器,即ContextLoaderListener,仅仅配置DispatcherSerlvet就可以胜任大多数任务了。而我自己的估计是因为一个Spring项目可以配置多个DispatcherServlet,此时就需要有一个根容器来存放多个DispatcherSerlvet之间的公共Bean。所以在有父子容器的项目中,多半建议Web层Bean交给DispatcherServlet的容器去管理,其他的Bean就给根容器去管理。


Spring Annotation 驱动 | Spring容器的初始化流程


Maybe有时间才能完成了,毕竟现在更多的是使用的是SpringBoot开发,已非曾经的Spring开发模式;所以之后可能会着重于完善对SpringBoot方面知识的补充


参考资料


  • emmm

你可能感兴趣的:(Spring)