源码级剖析ContextLoaderListener

源码级剖析ContextLoaderListener

前言

在Spring项目中,ContextLoaderListener扮演着至关重要的作用。项目启动需要ContextLoaderListener加载spring配置文件,所以经常在web.xml中来配置ContextLoaderListener。

<!-- 配置spring核心监听器,默认会以 /WEB-INF/applicationContext.xml作为配置文件 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- contextConfigLocation参数用来指定Spring的配置文件 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param> 

ContextLoaderListener是怎么工作的呢?通过ContextLoaderListener源码来分析一下

ContextLoaderListener

//ContextLoaderListener继承ContextLoader,实现ServletContextListener接口。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    //ContextLoaderListener在服务器启动时调用contextInitialized
    public void contextInitialized(ServletContextEvent event) {
        //此处ContextLoaderListener调用的ContextLoader的initWebApplicationContext方法
        this.initWebApplicationContext(event.getServletContext());
    }
    //ContextLoaderListener在服务器关闭时调用contextDestroyed
    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }

}
  1. ContextLoaderListener继承ContextLoader
  2. ContextLoaderListener实现ServletContextListener接口
  3. contextInitialized是ContextLoaderListener的关键核心方法,也是我们的重点研究对象

在这里插入图片描述
我们先来了解下ServletContextListener接口

ServletContextListener

ServletContextListener主要用于监听ServletContext对象的生命周期,服务器启动时 创建ServletContext,服务器关闭时销毁ServletContext

public interface ServletContextListener extends EventListener {
    //Servlet容器启动Web应用时调用该方法。在调用完该方法之后,初始化Filter,并且对那些在Web应用启动时就需要被初始化的Servlet 进行初始化
    default void contextInitialized(ServletContextEvent sce) {
    }
    //当Servlet容器终止Web应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet和Filter过滤器。
    default void contextDestroyed(ServletContextEvent sce) {
    }
}

当servlet容器启动或终止时,触发ServletContextEvent事件。ServletContextEvent作为参数参与了ContextLoader的initWebApplicationContext方法。

ContextLoaderListener调用父类ContextLoader的initWebApplicationContext方法,接下来重点看一下ContextLoader类。

ContextLoader

在看ContextLoader的initWebApplicationContext的方法前,先来看下ContextLoader的静态代码块
在这里插入图片描述

1. 静态代码块

    static {
        try {
            //DEFAULT_STRATEGIES_PATH = "ContextLoader.properties",此处加载的是contextLoader.properties的配置文件
            ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        } catch (IOException var1) {
            throw new IllegalStateException("Could not load 'ContextLoader.properties': " + var1.getMessage());
        }

        currentContextPerThread = new ConcurrentHashMap(1);
    }

源码里翻看下contextLoader.properties里是怎么设置的?接着翻

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

ContextLoader通过静态代码块读取XmlWebApplicationContext,在用户没有在配置文件指定CONTEXT_CLASS的时候,作为默认值使用。

2. initWebApplicationContext

看完静态代码块,重头戏来了,看看核心方法initWebApplicationContext。

public WebApplicationContext initWebApplicationContext(
        ServletContext servletContext) {
    
    // 检查application对象中是否有ApplicationContext,有则抛出异常
    // 其中ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
    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!");
        // 无法初始化上下文,因为已经存在根应用程序上下文
        // web.xml中定义了多个ContextLoader
    }else { //否则就初始化WebApplicationContext,"Initializing Spring root WebApplicationContext"这句 是不是很熟悉?
            servletContext.log("Initializing Spring root WebApplicationContext");
            Log logger = LogFactory.getLog(ContextLoader.class);
            if (logger.isInfoEnabled()) {
                logger.info("Root WebApplicationContext: initialization started");
            }
 
    /**
    *
    * 创建得到WebApplicationContext
    * 将createWebApplicationContext返回值转换为ConfigurableWebApplicationContext类型
    */
    
    if (this.context == null) {
        //调用createWebApplicationContext,创建WebApplicationContext,看步骤2.1
        this.context = createWebApplicationContext(servletContext);
    }
    
    // 拿到ConfigurableWebApplicationContext进入if判断
    if (this.context instanceof ConfigurableWebApplicationContext) {
        
        // 强制转换为ConfigurableWebApplicationContext类型
        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
        
        // cwac(ApplicationContext)没有生效,配置文件没有加载
        if (!cwac.isActive()) {
            //applicationContext没有刷新,要设置一个parent 
            if (cwac.getParent() == null) {
                ApplicationContext parent = this.loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }
            // 加载配置文件
            // 此处调用了configureAndRefreshWebApplicationContext方法,看步骤2.2
            this.configureAndRefreshWebApplicationContext(cwac, servletContext);
        }
    }
    
    // 将WebApplicationContext绑定到web应用程序的ServtContext上
    servletContext.setAttribute(
            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
            this.context);
    
    // 返回webApplicationContext
    return this.context;
}

init中有两个核心方法

  • createWebApplicationContext()
  • configureAndRefreshWebApplicationContext()

先来看看如何创建webApplicationContext

2.1 createWebApplicationContext
    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        // 获取context类, 看步骤2.1.1
        Class<?> contextClass = this.determineContextClass(sc);//判断使用什么样的类在Web容器中作为IoC容器
        
        如果context类没有实现ConfigurableWebApplicationContext接口则抛出异常      
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        } else {
             返回该context类的实例,调用BeanUtils.instantiateClass(contextClass),通过反射,调用XmlWebApplicationContext的无参构造函数实例化XmlWebApplicationContext对象
            return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        }
    }
2.1.1 determineContextClass
    /**
    * 返回context
    */
    protected Class<?> determineContextClass(ServletContext servletContext) {
        //从servletContext中获取初始化配置参数contextClass,配置类实现了XmlWebApplicationContext
        String contextClassName = servletContext.getInitParameter("contextClass");//读取web.xml中的配置<context-param>contextClass</context-param>
        //有配置使用配置的CONTEXT_CLASS,默认使用XmlWebApplicationContext.
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            } catch (ClassNotFoundException var4) {
                throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
            }
        } else {
            // 如果没有配置则使用XmlWebApplicationContext,此处读取的contextLoad.properties配置
            //
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            } catch (ClassNotFoundException var5) {
                throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
            }
        }
    }
  1. contextClass继承了XmlWebApplicationContext
  2. XmlWebApplicationContext继承了AbstractRefreshableWebApplicationContext,调用了getConfigLocations()
    // 该方法默认加载contextConfigLocation中定义的xml 文件
    public String[] getConfigLocations() {
        return super.getConfigLocations();
    }
  1. AbstractRefreshableWebApplicationContext继承了AbstractRefreshableConfigApplicationContext
	// this.getDefaultConfigLocations()返回null
    @Nullable
    protected String[] getConfigLocations() {
        return this.configLocations != null ? this.configLocations : this.getDefaultConfigLocations();
    }

在这里插入图片描述
此方法直接返回AbstractRefreshableConfigApplicationContext自己的属性:String[] configLocations,ContextLoader完成了对contextConfigLocation中的spring配置文件的加载。

终于找到了ContextLoaderListener是如何加载spring配置文件的,但是init方法还没结束,接下来我们看看如何初始化WebApplicationContext。

2.2 configureAndRefreshWebApplicationContext
    // 配置并且刷新WebApplicationContext,设置IoC容器的参数,并通过refresh开启容器初始化
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        String configLocationParam;
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            configLocationParam = sc.getInitParameter("contextId");
            if (configLocationParam != null) {
                wac.setId(configLocationParam);
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }
        //为wac绑定servletContext
        wac.setServletContext(sc);
        
        //CONFIG_LOCATION_PARAM=contextConfigLocation
        //getInitParameter(CONFIG_LOCATION_PARAM)解释了为什么配置文件中需要有contextConfigLocation项
        //sevletConfig.getInitParameter和servletContext.getInitParameter作用范围是不一样的
        configLocationParam = sc.getInitParameter("contextConfigLocation");
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }

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

        this.customizeContext(sc, wac);
        wac.refresh();//容器的初始化
    }

总结

做个总结,先上图:
在这里插入图片描述

  • ContextLoadListener实现了ServletContextListener接口,重写了init和distory方法

    • ServletContextListener主要监听ServletContext对象的生命周期,在ServletContext对象创建后,webApplicationContext才能被创建并绑定到ServletContext对象
    • 绑定以后就可以通过servletContext获取webApplicationContext了
  • ContextLoadListener继承了ContextLoader类

    • 通过静态代码块获取XmlWebApplicationContext作为默认的CONTEXT_CLASS,用于WebApplicationContext的创建
    • init方法中create方法用于创建WebApplicationContext,configureAndRefresh用于初始化WebApplicationContext
      • create方法中通过determineContextClass方法,最终使用AbstractRefreshableConfigApplicationContext类的getConfigLocations()
        加载spring的配置文件
      • configureAndRefresh结束完成了WebApplicationContext的初始化工作。

好了,讲到这里,ContextLoadListener已经说完了!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值