SpringMVC源码:SpringMVC中的父子容器

回顾

我们会在web.xml中配置监听器:ContextLoaderListener。本文就来介绍这个监听器在背后做了什么

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applictionContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

配置了ContextLoaderListener,那么在当前web应用启动时,就会生成一个Spring根容器(默认类型为XmlWebApplicationContext),并且将其存放在 ServletContext 域中(被整个应用所共享)

web应用启动 —> 创建ServletContext 对象 (ContextLoaderListener 监听ServletContext 对象) —> ServletContext对象创建完成 ,ContextLoaderListener 便会随即生成Spring根容器

ContextLoaderListener 是一个监听器,实现了 ServletContextListener 接口(负责监听 ServletContext 对象)(可以先看看ContextLoaderListener的继承结构图)

复习:JavaWeb 中的监听器

ServletContext 对象创建完成时,Tomcat 会调用 ServletContextListener 的 contextInitialized 方法

ServletContext 对象销毁前,Tomcat 会调用 ServletContextListener 的 contextDestroyed 方法

public interface ServletContextListener extends EventListener {
	public void contextInitialized ( ServletContextEvent sce );	
	public void contextDestroyed ( ServletContextEvent sce );	
}

看一下 ContextLoaderListener 对 contextInitialized() 方法的实现

ContextLoaderListener # contextInitialized

@Override
public void contextInitialized(ServletContextEvent event) {
    initWebApplicationContext(event.getServletContext()); //调用父类 ContextLoader 的 initWebApplicationContext() 方法
}

初始化Spring根容器

ContextLoader # initWebApplicationContext

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    
    // 首先,先判断了当前 servletContext 的属性Attribute中是否已经有了Spring根容器,若有,则抛出异常提示(因为这才是第一次创建);
    // 注:Spring根容器默认会保存在 ServletContext 的属性Attribute中,key 固定为 "org.springframework.web.context.WebApplicationContext.ROOT"
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException("...");
    }

    //...

    try {
        /*
           1、创建Spring根容器(父容器):WebApplicationContext 对象
        */
        if (this.context == null) {
            this.context = createWebApplicationContext(servletContext);
        }
        /*
           2.1、为Spring根容器设置一个父类,这个是spring默认的行为,一般设置的父类都为null (不重要)
           2.2、配置并且刷新根容器
        */
        // java套路:在上一步的createWebApplicationContext()中返回的类型已经是ConfigurableWebApplicationContext类型的了,这里为什么又判断了一下呢?
        // 1、有可能有子类重写了createWebApplicationContext()方法,自定义了自己的创建容器的逻辑,返回的容器可能不再是ConfigurableWebApplicationContext类型的了,所以这里要进行判断
        // 2、有可能在 this.context 在此方法执行前已经被赋予了值(以JavaConfig的方式配置MVC)
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; // 强转为 ConfigurableWebApplicationContext 类型
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    ApplicationContext parent = loadParentContext(servletContext);  // 点进去可以发现,返回的是null
                    cwac.setParent(parent); // 根容器的父类设置为null
                }
                // 配置并且刷新根容器
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        
        /*
        	3、将Spring根容器设置到 servletContext 的 Attribute 域中,根容器对应的key为"org.springframework.web.context.WebApplicationContext.ROOT"
        */
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

        //...

        return this.context;
        
    }// catch 

}

1、创建Spring根容器

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {

    Class<?> contextClass = determineContextClass(sc); //得到根容器的Class类型
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("..."); //若自定义了Spring根容器,那么此容器类型必须为ConfigurableWebApplicationContext类型,否则抛异常
    }
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); //使用 BeanUtils 实例化 Class
}

protected Class<?> determineContextClass(ServletContext servletContext) {
    String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);    
    //若在配置ServletContext时,param name配置了"contextClass"属性,那么就使用其值,进行反射创建,作为Spring根容器
    if (contextClassName != null) {
        try {
            return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        }
    }
    //若没有配置,则使用默认策略defaultStrategies(使用 XmlWebApplicationContext 作为Spring根容器)
    else {
        contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
        try {
            return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
        }
    }
}

若配置 ServletContext 时,param name配置了contextClass,即可以指定使用我们自定义的Spring根容器: 则会根据param value的值进行反射实例化(一般情况下,我们不会配置)

<context-param>
    ...
    <param-name> contextClass </param-name>
    <param-value>com.itcode.springdemo.MyWebApplicationContext</param-value>
</context-param>

若没有配置,则使用默认策略defaultStrategies:使用 ContextLoader.properties 中配置的 XmlWebApplicationContext 类 (ContextLoader.properties文件位于ContextLoader.class源码的同级目录下)

注:ContextLoader.properties 的内容是在何时被读到 “defaultStrategies” 中去的?

ContextLoader 类中,有静态代码块:

	private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";	
	
	private static final Properties defaultStrategies;

	static {
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized by application developers.
		try {
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		//catch (..) {...}
	}

ContextLoader.properties 文件内容:

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
总结:

Spring根容器的创建依据:1、如果在ServletContext 的配置中,配置了contextClass 参数,则使用其配置的值,作为 Spring根容器。(一般情况下我们不会这么做),

2、否则会使用 XmlWebApplicationContext 来作为 Spring根容器(默认都是这样)

2、配置并刷新根容器

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
   
    // 配置根容器的id
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); 
        // 若在配置ServletContext时,param name配置了"contextId"属性,那么就使用其值,作为id。  若没有配置,则默认生成 id...
        if (idParam != null) {
            wac.setId(idParam);
        }
        else {
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }

	// 配置根容器的 ServletContext 属性
    wac.setServletContext(sc);
    // 配置根容器的 configLocations 属性 
    // 注:XmlWebApplicationContext在载入BeanDefinition时(后续根容器的刷新阶段),利用的就是此属性。 即在web.xml中配置的"classpath:applictionContext.xml" 
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        // 若在配置ServletContext时,param name配置了"contextConfigLocation"属性,那么就将其值,设置到根容器的configLocations属性中
        wac.setConfigLocation(configLocationParam);
    }

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

    // 初始化自定义配置(待...)
    customizeContext(sc, wac);
    
    // 刷新根容器
    wac.refresh();
}
总结:

在此阶段完成了对Spring根容器的初始化和刷新 (可以先看一下XmlWebApplicationContext的继承结构图)

依据web.xml中的配置,初始化了根容器(XmlWebApplicationContext)中的一些属性,如:setIdCalled、configLocations(后续刷新阶段载入BeanDefinition的依据)、servletContext

并完成了根容器的刷新

3、添加根容器到servletContext的Attribute域中

将根容器设置到 ServletContext 的 Attribute 域中,根容器对应的key为 “org.springframework.web.context.WebApplicationContext.ROOT”

这样的话,这个根容器就可以被多个子容器所共享

初始化Spring根容器【总结】

0、Spring根容器,是由监听器ContextLoaderListener创建的。创建时机:web应用中的 ServletContext 对象创建完成之后。

1、我们可以在web.xml中配置 ServletContext 时,间接地配置Spring根容器

<context-param>
    <!-- contextClass属性:自定义决定Spring根容器-->
    <param-name> contextClass </param-name>
    <param-value>com.itcode.springdemo.MyWebApplicationContext</param-value>
    <!-- contextId属性:配置根容器的id-->
    <param-name> contextId </param-name>
    <param-value>...</param-value>
    <!-- contextConfigLocation属性:设置Spring根容器的BeanDefinition加载路径-->         <!--只有此处是常用的-->
    <param-name>contextConfigLocation</param-name>                     
    <param-value>classpath:applictionContext.xml</param-value>    
</context-param>

2、默认创建的Spring根容器类型为 XmlWebApplicationContext (也可以通过web.xml中在中配置contextClass属性,来指定自定义的Spring根容器(不常用))

3、完成了容器的刷新

3、Spring根容器创建完成后,存放在 ServletContext 域中,key为 “org.springframework.web.context.WebApplicationContext.ROOT”

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值