百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

Spring MVC源码分析容器初始化之Root WebApplicationContext容器

xsobi 2024-11-24 23:34 2 浏览

概述

随着Spring Boot逐渐全面覆盖到我们的项目中,我们已经基本忘记了经典的sevlet+Spring MVC的组合,那些让人熟悉的web.xml配置,而本文,我们想抛开Spring Boot到一旁,回到从前,一起来看看Servlet是怎么和Spring MVC集成的,怎么来初始化Spring容器的。

在开始查看具体源码的之前,我们先看一下web.xml的配置,代码如下:

	<!--[1] spring配置-->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
	<!-- 指定spring bean的配置文件所在目录,默认配置是在WEB-INF目录下 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:config/applicationContext.xml</param-value>
	</context-param>
	
	<!-- [2] Spring MVC的配置 -->
	<servlet>
		<servlet-name>spring</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<!-- 也可以自定义servlet.xml的配置文件的位置,默认为WEB-INF目录下,名称为[servlet-name]-servlet.xml, eg: spring-servlet.xml -->
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>WEB-INF/spring-servlet.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>spring</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>
  • [1]处配置了org.springframework.web.context.ContextLoaderListener对象,这是一个javax.servlet.ServletContextListener对象,会初始化一个Root Spring WebApplicationContext容器,
  • [2]处配置了org.springframework.web.servlet.DispatcherServlet对象,这是一个javax.servlet.http.HttpServlet对象,它拦截了所有的*do的请求,也会初始化一个属于它的WebApplicationContext容器。并且这个容器一[1]处的容器作为父容器。

如何调试

执行ContextLoaderTests#testContextLoaderListenerWithDefaultContext()单元测试方法。

Root WebApplicationContext容器

在概述中,我们可以知道Root WebApplicationContext容器的初始化,是有ContextLoaderListener来实现的,在Servlet容器启动的时候,则会被ServletContextListener监听到,从而调用contextInitialized(ServletContextEvent event)方法,初始化Root WebApplicationContext容器。

而ContextLoaderListener的类图如下:

ServletContextListener和EventListener是servlet的类,ContextLoaderListener和ContextLoader是spring的类

ContextLoaderListener

org.springframework.web.context.ContextLoaderListener, 实现ServletContextListener接口,继承了ContextLoader类,实现了Servlet容器的启动和销毁时,分别初始化和销毁WebApplicationContext容器。

构造方法

public ContextLoaderListener() {}

public ContextLoaderListener(WebApplicationContext context){
    super(context);
}

这两个构造方法,是因为父类ContextLoader就有两个构造方法,所以必须重新定义,需要注意的是:第二个构造方法,可以直接传递一个WebApplicationContext对象,那样,实际ContextLoaderListener就无需创建WebApplicationContext对象。

contextInitialized

@Override
public void contextInitialized(ServletContextEvent event){
    initWebApplicationContext(event.getServletContext);
}

ServletContextListener监听Servlet容器启动,调用contextInitialized(ServletContextEvent event)方法,而ContextLoaderListener重新了contextInitialized的方法,调用的是父类ContextLoader的initWebApplicationContext(ServletContext sc)方法,初始化WebApplicationContext对象。

contextDestroyed

public void contextDestroyed(ServletContextEvent event){
    closeWebApplicationContext(event.getServletContext);
    ContextCleanupListener.cleanupAttributies(event.getServletContext);
}

销毁WebApplicationContext容器。

ContextLoader

org.springframework.web.context.ContextLoader。真正实现WebApplicationContext容器初始化和销毁逻辑的类

构造方法

因为ContextLoader的属性比较多,

  1. defaultStrategies静态属性,默认的配置Properties对象。
private static final String default_strategies_path = "ContextLoader.properties";

private static final Properties defaultStrategies;

static {
    try{
        ClassPathResource resource = new ClassPathResource(default_strategies_path, ContextLoader.class);
        defaultStrategies = PropertiesUtils.loadProperties(resource);
    }catch(Exception e){
        
    }
}

从ContextLoader.defaultStrategies中,读取默认的properties对象,实际上这是一个应用开发者无需关系的配置,而是Spring框架自身所定义的。配置的spring-web/src/main/resources/org/springframework/web/context/ContextLoader.properties

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

这就是意味着,我们没有配置context-param标签中配置指定的WebApplicationContext类型,就是要默认的XmlWebApplicationContext类型。一般情况下,无需指定。

  1. context属性,Root WebApplicationContext对象
private WebApplicationContext context;

public ContextLoader(){}

public ContextLoader(WebApplicationContext context){
    this.context = context;
}

可以看出,如果传递了WebApplicationContext,则不会进行初始化。重新创建.

initWebApplicationContext

initWebApplicationContext(ServletContext sc)方法,初始化WebApplicationContext对象。

public WebApplicationContext initWebApplicationContext(ServletContext sc){
    // 【1】如果已经存在WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE对应的WebApplicationContext对象,抛出异常,防止在web.xml中存在多个ContextLoader
    if(sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null){
        throw new IllegalStateExecption();
    }
    
    //【2】 打印日志
    sc.log("Initializing Spring root WebApplicationContext");
	Log logger = LogFactory.getLog(ContextLoader.class);
	if (logger.isInfoEnabled()) {
		logger.info("Root WebApplicationContext: initialization started");
	}
	
	// 记录开始时间
	long startTime = System.currentTimeMillis();
	
	try{
	    if (context == null){
	        //【3】 如果没有创建context对象,则创建context对象
	        this.context = createWebApplicationContext(sc);
	    }
	    
	    // 【4】如果是ConfigurableWebApplicationContext的子类,如果为刷新,则进行配置和刷新
	    if (this.context instanceof ConfigurableWebApplicationContext){
	        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
	        if(!cwac.isActive){
	            //【4.1】未刷新,也就是未激活
	            if(cwac.getParent == null){
	                // 【4.2】无父容器,则进行加载和配置
	                ApplicationContext parent = loadParentContext(sc);
	                cwac.setParent(parent);
	            }
	            
	            // 【4.3】配置context对象,并进行刷新
	            configureAndRefreshWebApplicationContext(cwac, sc);
	        }
	    }
	    
	    // 【5】记录在ServletContext中
	    sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
	    
	    // 【6】记录到currentContext或者currentContextPerThread中
	    ClassLoader ccl = Thread.currentThread().getContextClassLoader();
	    if(ccl == ContextLoader.class.getClassLoader){
	        currentContext = this.context;
	    }else if(ccl != null){
	        currentContextPreThread.put(ccl, this.context);
	    }
	    
	    // 【7】打印日志
	    
	    // 【8】返回context对象
	    return this.context;
	}catch(RunTimeException | Error ex){
	    // 【9】当发生异常,记录到WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE中。不再重新初始化了。
	    sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUT, ex);
	    throw ex;
	}
}

【3】处调用的是createWebApplicationContext(ServletContext sc)方法,初始化context,也就是创建WebApplicationContext对象。

【4】处,如果context是ConfigurableWebApplicationContext的子类,如果为刷新,则进行配置和刷新

【4.1】处,默认情况下未进行激活,

【4.2】处,无父容器。则进行加载和配置,默认情况下loadParentContext(ServletContext sc)方法,返回的是null

protected ApplicationContext loadParentContext(ServletContext sc){
    return null;
}

这是一个让子类实现的方法,当然子类ContextLoaderListener并没有重写该方法,

【4.3】处,调用了configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc);方法。配置ConfigurableWebApplicationContext对象。并进行刷新。

【5】处,记录context在ServletContext中,

【6】处,记录到currentContext或者currentContextPreThread中,差异在于类加载器的不同,变量代码如下:

private static fianl Map<ClassLoader, WebApplicationContext> currentContextPreThread = new ConCurrentHashMap<>(1);

private static volitile WebApplicationContext currentContext;

这两个变量,在销毁Spring WebApplication容器时会用到

createWebApplicationContext

createWebApplicationContext(ServletContext sc)方法。初始化context,即创建WebApplicationContext对象。

public WebApplicationContext createWebApplicationContext(ServletContext sc){
    // 【1】获得context的类
    Class<?> contextClass = determineContextClass(sc);
    // 【2】判断contextClass是否符号ConfigurableWebApplication的类型
    if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)){
        throw new ApplicationContextException();
    }
    
    // 【3】创建WebApplicationContext的类的对象
    return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
}

【1】处调用了determineContextClass(ServletContext sc)方法,获得context的类。

public static final String context_class_param = "contextClass";

protected Class<?> determineContextClass(ServletContext sc){
    // 获得参数contextClass的值
    String contextClassName= sc.getInitParamter(context_class_param);
    // 【1】如果只非空,则获得该类
    if(contextClassName != null){
        try{
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
    }else{
        // 【2】从defaultStrategies获得该类
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try{
            return ClassUtils.forName(contextClassName, ClassLoader.class.getClassLoader());
        }
    }
}

分两种情况,【1】是从ServletContext配置的context类,【2】是从ContextLoader.properties配置的context类。

默认情况下,我们不会主动在ServletContext配置context类。所以基本上使用的都是ContextLoader.properties配置的contex类,即XmlWebApplicationContext类。

configureAndRefreshWebApplicationContext

ConfigurableAndRefreshWebApplicationContext(ConfigurableWebApplicationContext cwac, ServletContext sc);方法。配置ConfigurableWebApplicationContext对象。并进行刷新。


public static final String contex_id_param = "contextId";

public static final String config_location_param = "contextConfigLocation";

public void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext cwac, ServletContext sc){
    // 【1】如果cwac使用了默认的编号,这重新设置contextId属性
    if(ObjectUtils.identityToString(cwac).equals(cwac.getId())){
        // 【1.1】使用contextId属性
        String contextId = sc.getInitParamter(contex_id_params);
        if(contextId != null){
            cwac.setId(contextId);
        }else{
            // 【1.2】自动生成
            cwac.setId(ConfigurableWebApplicationContext.application_context_id_prefix + ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }
    
    // 【2】设置context的ServletContext属性
    cwac.setServletContext(sc);
    // 【3】设置context配置文件的地址
    String configLocationParam = sc.getInitParamter(contextConfigLocation);
    if(null != configLocationParam){
        cwac.setConfigLocation(configLocationParam);
    }
    
    ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
	}
	
	// 【5】执行自定义初始
	customizeContext(sc, cwac);
	
	// 【6】刷新context,执行初始化
	cwac.refresh();
}

【3】处,设置了context的配置文件地址,

【6】处,刷新cwac,执行初始化,此处,就会进行一些Spring容器的初始化。

closeWebApplicationContext

closeWebApplicationContext(ServletContext sc)方法,关闭WebApplication容器对象

public void closeWebApplicationContext(ServletContext sc){
    try{
        if(this.context instaceof ConfigurableWebApplicationContext){
            this.context.close();
        }
    }finally{
        // 移除currentContext和currentContextPreThread
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if(ccl == ContextLoader.class.getClassLoader()){
            currentContext = null;
        }else if(ccl != null){
            currentContextPreThread.remove(ccl);
        }
        
        // 从ServletContext中移除
        sc.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    }
}

相关推荐

js向对象中添加元素(对象,数组) js对象里面添加元素

一、添加一个元素对象名["属性名"]=值(值:可以是一个值,可以是一个对象,也可以是一个数组)这样添加进去的元素,就是一个值或对象或数组...

JS小技巧,如何去重对象数组?(一)

大家好,关于数组对象去重的业务场景,想必大家都遇到过类似的需求吧,这对这样的需求你是怎么做的呢。下面我就先和大家分享下如果是基于对象的1个属性是怎么去重实现的。方法一:使用.filter()和....

「C/C++」之数组、vector对象和array对象的比较

数组学习过C语言的,对数组应该都不会陌生,于是这里就不再对数组进行展开介绍。模板类vector模板类vector类似于string,也是一种动态数组。能够在运行阶段设置vector对象的长度,可以在末...

如何用sessionStorage保存对象和数组

背景:在工作中,我将[{},{}]对象数组形式,存储到sessionStorage,然后ta变成了我看不懂的形式,然后我想取之用之,发现不可能了~记录这次深刻的教训。$clickCouponIndex...

JavaScript Array 对象 javascript的array对象

Array对象Array对象用于在变量中存储多个值:varcars=["Saab","Volvo","BMW"];第一个数组元素的索引值为0,第二个索引值为1,以此类推。更多有...

JavaScript中的数组Array(对象) js array数组

1:数组Array:-数组也是一个对象-数组也是用来存储数据的-和object不同,数组中可以存储一组有序的数据,-数组中存储的数据我们称其为元素(element)-数组中的每一个元素都有一...

数组和对象方法&amp;数组去重 数组去重的5种方法前端

列举一下JavaScript数组和对象有哪些原生方法?数组:arr.concat(arr1,arr2,arrn);--合并两个或多个数组。此方法不会修改原有数组,而是返回一个新数组...

C++ 类如何定义对象数组?初始化数组?linux C++第43讲

对象数组学过C语言的读者对数组的概念应该很熟悉了。数组的元素可以是int类型的变量,例如int...

ElasticSearch第六篇:复合数据类型-数组,对象

在ElasticSearch中,使用JSON结构来存储数据,一个Key/Value对是JSON的一个字段,而Value可以是基础数据类型,也可以是数组,文档(也叫对象),或文档数组,因此,每个JSON...

第58条:区分数组对象和类数组对象

示例设想有两个不同类的API。第一个是位向量:有序的位集合varbits=newBitVector;bits.enable(4);bits.enable([1,3,8,17]);b...

八皇后问题解法(Common Lisp实现)

如何才能在一张国际象棋的棋盘上摆上八个皇后而不致使她们互相威胁呢?这个著名的问题可以方便地通过一种树搜索方法来解决。首先,我们需要写一个函数来判断棋盘上的两个皇后是否互相威协。在国际象棋中,皇后可以沿...

visual lisp修改颜色的模板函数 怎么更改visual studio的配色

(defunBF-yansemokuai(tuyuanyanse/ss)...

用中望CAD加载LISP程序技巧 中望cad2015怎么加载燕秀

1、首先请加载lisp程序,加载方法如下:在菜单栏选择工具——加载应用程序——添加,选择lisp程序然后加载,然后选择添加到启动组。2、然后是添加自定义栏以及图标,方法如下(以...

图的深度优先搜索和广度优先搜索(Common Lisp实现)

为了便于描述,本文中的图指的是下图所示的无向图。搜索指:搜索从S到F的一条路径。若存在,则以表的形式返回路径;若不存在,则返回nil。...

两个有助于理解Common Lisp宏的例子

在Lisp中,函数和数据具有相同的形式。这是Lisp语言的一个重大特色。一个Lisp函数可以分析另一个Lisp函数;甚至可以和另一个Lisp函数组成一个整体,并加以利用。Lisp的宏,是实现上述特色的...