spring学习--ContextLoaderListener工作原理

Posted GLLegolas

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring学习--ContextLoaderListener工作原理相关的知识,希望对你有一定的参考价值。

在spring Web中,需要初始化IOC容器,用于存放我们注入的各种对象。当tomcat启动时首先会初始化一个web对应的IOC容器,用于初始化和注入各种我们在web运行过程中需要的对象。当tomcat启动的时候是如何初始化IOC容器的,我们先看一下在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>

ContextLoaderListener是一个监听器,其实现了ServletContextListener接口,其用来监听Servlet,当tomcat启动时会初始化一个Servlet容器,这样ContextLoaderListener会监听到Servlet的初始化,这样在Servlet初始化之后我们就可以在ContextLoaderListener中也进行一些初始化操作。看下面的ServletContextListener的源码也是比较简单的,ContextLoaderListener实现了ServletContextListener接口,所以会有两个方法contextInitialized和contextDestroyed。web容器初始化时会调用方法contextInitialized,web容器销毁时会调用方法contextDestroyed。

 
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
 
    public ContextLoaderListener() {
    }
 
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
    /**
     * Initialize the root web application context.
     */
    @Override
    public void contextInitialized(ServletContextEvent event) {
        //在父类ContextLoader中实现
        initWebApplicationContext(event.getServletContext());
    }
    /**
     * Close the root web application context.
     */
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
 
}

 

ContextLoaderListener的方法contextInitialized()的默认实现是在他的父类ContextLoader的initWebApplicationContext方法中实现的,意思就是初始化web应用上下文。他的主要流程就是创建一个IOC容器,并将创建的IOC容器存到servletContext中,ContextLoader的核心实现如下:

initWebApplicationContext函数:

//初始化WebApplicationContext,IOC容器
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        //判断web容器中是否已经有WebApplicationContext
        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!");
        }
 
        Log logger = LogFactory.getLog(ContextLoader.class);
        servletContext.log("Initializing Spring root WebApplicationContext");
        if (logger.isInfoEnabled()) {
            logger.info("Root WebApplicationContext: initialization started");
        }
        long startTime = System.currentTimeMillis();
 
        try {
            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            //创建WebApplicationContext
            if (this.context == null) {
                //最终得到的是XmlWebApplicationContext
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                //判断应用上下文是否激活
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    //判断是否已经有父容器
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent ->
                        // determine parent for root web application context, if any.
                        //获得父容器
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    //设置并刷新WebApplicationContext容器
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            //将初始化的WebApplicationContext设置到servletContext中
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
 
            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            }
            else if (ccl != null) {
                currentContextPerThread.put(ccl, this.context);
            }
 
            if (logger.isDebugEnabled()) {
                logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
            }
            if (logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
            }
            //初始化 WebApplicationContext完成并返回
            return this.context;
        }
        catch (RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
        catch (Error err) {
            logger.error("Context initialization failed", err);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
            throw err;
        }
    }

springMVC父容器初始化流程图(盗图)如下:

 

技术图片

完整的ContextLoader源码如下:

 

 

/**
 * 用于初始化ApplicationContext
 *
 */
public class ContextLoader {
 
    //WebApplicationContext id
    public static final String CONTEXT_ID_PARAM = "contextId";
    
    //配置文件
    public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
 
    public static final String CONTEXT_CLASS_PARAM = "contextClass";
 
    public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
 
    
    public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";
 
    
    public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
 
    public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
 
    private static final String INIT_PARAM_DELIMITERS = ",; 	
";
 
    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 (IOException ex) {
            throw new IllegalStateException("Could not load ‘ContextLoader.properties‘: " + ex.getMessage());
        }
    }
    private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread =
            new ConcurrentHashMap<ClassLoader, WebApplicationContext>(1);
    
    private static volatile WebApplicationContext currentContext;
    
    private WebApplicationContext context;
    
    private BeanFactoryReference parentContextRef;
 
    private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =
            new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
 
    public ContextLoader() {
    }
 
    public ContextLoader(WebApplicationContext context) {
        this.context = context;
    }
 
    @SuppressWarnings("unchecked")
    public void setContextInitializers(ApplicationContextInitializer<?>... initializers) {
        if (initializers != null) {
            for (ApplicationContextInitializer<?> initializer : initializers) {
                this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);
            }
        }
    }
 
    //初始化WebApplicationContext,IOC容器
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        //判断web容器中是否已经有WebApplicationContext
        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!");
        }
 
        Log logger = LogFactory.getLog(ContextLoader.class);
        servletContext.log("Initializing Spring root WebApplicationContext");
        if (logger.isInfoEnabled()) {
            logger.info("Root WebApplicationContext: initialization started");
        }
        long startTime = System.currentTimeMillis();
 
        try {
            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            //创建WebApplicationContext
            if (this.context == null) {
                //最终得到的是XmlWebApplicationContext
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                //判断应用上下文是否激活
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    //判断是否已经有父容器
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent ->
                        // determine parent for root web application context, if any.
                        //获得父容器
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    //设置并刷新WebApplicationContext容器
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
            //将初始化的WebApplicationContext设置到servletContext中
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
 
            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            }
            else if (ccl != null) {
                currentContextPerThread.put(ccl, this.context);
            }
 
            if (logger.isDebugEnabled()) {
                logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
            }
            if (logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
            }
            //初始化 WebApplicationContext完成并返回
            return this.context;
        }
        catch (RuntimeException ex) {
            logger.error("Context initialization failed", ex);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
        catch (Error err) {
            logger.error("Context initialization failed", err);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
            throw err;
        }
    }
 
    
    //在web容器加载的时候初始化根WebApplicationContext
    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        //通过反射获得上下文类
        Class<?> contextClass = determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
        //初始化WebApplicationContext,是其实现类XmlWebApplicationContext
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
 
    
    protected Class<?> determineContextClass(ServletContext servletContext) {
        //查看servlet是否已经存在应用上下文类名
        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
        if (contextClassName != null) {
            try {
                //类名不为空则反射获得类
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            }
            catch (ClassNotFoundException ex) {
                throw new ApplicationContextException(
                        "Failed to load custom context class [" + contextClassName + "]", ex);
            }
        }
        else {
            //如果应用上下文类名为空,则获得默认的context上下文名
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            }
            catch (ClassNotFoundException ex) {
                throw new ApplicationContextException(
                        "Failed to load default context class [" + contextClassName + "]", ex);
            }
        }
    }
 
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            // The application context id is still set to its original default value
            // -> assign a more useful id based on available information
            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
            //设置应用上下文id,在web.xml中可以进行配置
            if (idParam != null) {
                wac.setId(idParam);
            }
            else {
                // Generate default id...
                //如果没有配置则设置默认名称
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }
        //设置ServletContext到容器中
        wac.setServletContext(sc);
        //获得web.xml中配置的contextConfigLocation的值,一般是classpath:applicationContext.xml
        String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (configLocationParam != null) {
            //设置加载文件地址
            wac.setConfigLocation(configLocationParam);
        }
 
        // The wac environment‘s #initPropertySources will be called in any case when the context
        // is refreshed; do it eagerly here to ensure servlet property sources are in place for
        // use in any post-processing or initialization that occurs below prior to #refresh
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
        }
 
        customizeContext(sc, wac);
        //这一步是特别关键的,这样application.xml中的所有配置都会被初始化到XMLWebApplication中
        wac.refresh();
    }
 
    protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
        List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
                determineContextInitializerClasses(sc);
 
        for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
            Class<?> initializerContextClass =
                    GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
            if (initializerContextClass != null) {
                Assert.isAssignable(initializerContextClass, wac.getClass(), String.format(
                        "Could not add context initializer [%s] since its generic parameter [%s] " +
                        "is not assignable from the type of application context used by this " +
                        "context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(),
                        wac.getClass().getName()));
            }
            this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
        }
 
        AnnotationAwareOrderComparator.sort(this.contextInitializers);
        for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
            initializer.initialize(wac);
        }
    }
 
    protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
            determineContextInitializerClasses(ServletContext servletContext) {
 
        List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
                new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();
 
        String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
        if (globalClassNames != null) {
            for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
                classes.add(loadInitializerClass(className));
            }
        }
 
        String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
        if (localClassNames != null) {
            for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
                classes.add(loadInitializerClass(className));
            }
        }
 
        return classes;
    }
 
    @SuppressWarnings("unchecked")
    private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) {
        try {
            Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
            Assert.isAssignable(ApplicationContextInitializer.class, clazz);
            return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz;
        }
        catch (ClassNotFoundException ex) {
            throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);
        }
    }
 
    
    //从ServletContext中获取父容器
    protected ApplicationContext loadParentContext(ServletContext servletContext) {
        ApplicationContext parentContext = null;
        String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
        String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
 
        if (parentContextKey != null) {
            // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
            BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
            Log logger = LogFactory.getLog(ContextLoader.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Getting parent context definition: using parent context key of ‘" +
                        parentContextKey + "‘ with BeanFactoryLocator");
            }
            this.parentContextRef = locator.useBeanFactory(parentContextKey);
            parentContext = (ApplicationContext) this.parentContextRef.getFactory();
        }
 
        return parentContext;
    }
 
    
    //通过给定的ServletContext来关闭IOC容器
    public void closeWebApplicationContext(ServletContext servletContext) {
        servletContext.log("Closing Spring root WebApplicationContext");
        try {
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ((ConfigurableWebApplicationContext) this.context).close();
            }
        }
        finally {
            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = null;
            }
            else if (ccl != null) {
                currentContextPerThread.remove(ccl);
            }
            servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
            if (this.parentContextRef != null) {
                this.parentContextRef.release();
            }
        }
    }
 
    //在当前线程中获取WebApplicationContext
    public static WebApplicationContext getCurrentWebApplicationContext() {
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl != null) {
            WebApplicationContext ccpt = currentContextPerThread.get(ccl);
            if (ccpt != null) {
                return ccpt;
            }
        }
        return currentContext;
    }
 
}

 

转自:http://blog.csdn.net/qq924862077/article/details/52769754

 

以上是关于spring学习--ContextLoaderListener工作原理的主要内容,如果未能解决你的问题,请参考以下文章

spring学习笔记 Spring概述

Spring学习2—Spring容器

优秀开源软件学习系列——从零学习Spring4以及学习方法分享

Spring学习

Spring的学习(一,spring的介绍)

Spring的学习(一,spring的介绍)