SpringMVC之ContextLoaderListener

Spring MVC 是建立在 IOC 容器基础上的,那么该容器是如何在 Web 环境中被载入并起作用的 ?

在我们的web.xml中有如下配置:

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext.xml
        </param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

context-param 用来指定 Spring IOC 容器 Bean 定义的XML文件的路径,ContextLoaderListener实现了ServletContextListener接口,与 Web 服务器的生命周期相关联,由ContextLoaderListener负责完成 IOC 容器在 Web 环境中的启动工作。

先来看看总体上的时序图:
在这里插入图片描述
ContextLoaderListener实现了ServletContextListener接口,该接口是 Servlet API 中定义的,提供了与 Servlet 生命周期结合的回调:contextInitialized 与 contextDestroyed。其中建立 WebApplicationContext 的过程是在 contextInitialized 中完成,具体交给了 ContextLoader , 关系如下

public class ContextLoaderListener extends ContextLoader implements ServletContextListener

关于 WebApplicationContext ,为了方便在 Web 环境中使用 IOC 容器, Spring 为 Web 应用提供了上下文的拓展接口 WebApplicationContext 来满足启动过程中的需求,默认加载 XmlWebApplicationContext。该类继承可追溯到 ApplicationContext , 在其基础上增加了 Web 环境和 XML 配置定义的处理。

ContextLoaderListener 创建的 WebApplicationContext 我们称之为 根上下文 ,在 DispatcherServlet 中也会创建一个 WebApplicationContext 我们称之为 子上下文,子上下文可以获得父上下文的 bean,对于子上下文 getBean 首先会去父上下文获取,得不到就去子上下文找,父上下文得不到子上下文的 bean,子上下文之间互相也访问不到。
根上下文的配置文件默认是 WEB-INF/appilicationContext.xml ,我们一般将除 Controller 之外的类在该文件中声明;DispatcherServlet 创建的子上下文的配置文件默认在 WEB-INF/xxx-servlet.xml (xxx指的是标签 <servlet-name> 的值),我们在该配置文件中声明 Controller bean。
根上下文 与 子上下文 都被存储在 ServletContext 中,如根上下文在ServletContext 中的key 为 WebApplicationContext.class.getName() + ".ROOT",想访问根上下文可以通过 WebApplicationContext.getWebApplicationContext (ServletContext sc) 获取。
在这里插入图片描述

ContextLoaderListener

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

ContextLoader#initWebApplicationContext

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    	//根上下文必须是唯一的
        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!");
        } else {
            ......

            try {
                if (this.context == null) {
                	//创建WebApplicationContext
                    this.context = this.createWebApplicationContext(servletContext);
                }

                ......
                }
				// 存储进servletContext中, key为 WebApplicationContext.class.getName() + ".ROOT
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
                //debug发现这里会将WebApplicationContext赋给currentContext变量
                ClassLoader ccl = Thread.currentThread().getContextClassLoader();
                if (ccl == ContextLoader.class.getClassLoader()) {
                    currentContext = this.context;
                } else if (ccl != null) {
                    currentContextPerThread.put(ccl, this.context);
                }

                ......

                return this.context;
            } catch (Error | RuntimeException var8) {
                ......
            }
        }
    }

来看看创建WebApplicationContext的逻辑

    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    	// 获得上下文WebApplicationContext的实现。默认是XmlWebApplicationContext
        Class<?> contextClass = this.determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName()
            	 + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        } else {
        	//反射得到WebApplicationContext对象
            return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        }
    }

determineContextClass

    protected Class<?> determineContextClass(ServletContext servletContext) {
    	// 若你在web.xml中设置的<context-param>里声明了
    	// contextClass的值,则直接利用反射得到其Class
        String contextClassName = servletContext.getInitParameter("contextClass");
        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
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

            try {
            	// 反射得到XmlWebApplicationContext.Class
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            } catch (ClassNotFoundException var5) {
                throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
            }
        }
    }

这里servletContext.getInitParameter("contextClass")获取web.xml中设置的contextClass属性的值,我们可以自己实现一个WebApplicationContext,然后在web.xml中如下配置

<context-param>
    <param-name>contextClass</param-name>
    <param-value>xxx</param-value>
</context-param>

<context-param>设置的是全局的信息,通过getInitPatameter获取获取。在每个<servlet>中还会有自己的<init-param>,通过该servlet的ServletConfiggetInitParameter方法获取。

如果没有在web.xml中配置,则加载默认实现类XmlWebApplicationContext,逻辑如下
defaultStrategies,在ContextLoader的静态代码块中

    static {
        try {
            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

XmlWebApplicationContext实例以键值对的形式存储在ServletContext中,其键为WebApplicationContext.class.getName() + ".ROOT"

上述就是IOC 容器在 Web 容器中的启动过程,在初始化这个上下文后,该上下文会被存储到 ServletContext 中,这样就建立了一个全局的关于整个应用的上下文。之后 DispatcherServlet 在进行自己持有的上下文的初始化时,将该全局上下文设为自己的父上下文。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值