Spring的ContextLoaderListener加载上下文的源码分析

前言:

1,如果使用自定义的监听器,需要经过下面的步骤 1到步骤10

2,如果使用Spring自己的监听器ContextLoaderListener,需要经过下面的步骤6到步骤10

3,web.xml中的加载顺序:context-param -> listener -> filter -> servlet

 

上下文加载步骤分析:

1,在WebService的 web.xml可以定义一个监听器:WebContextLoaderListener:

<listener>
    <listener-class>com.dangdang.ddframe.web.WebContextLoaderListener</listener-class>
</listener>

WebContextLoaderListener类:

package com.dangdang.ddframe.web;

public final class WebContextLoaderListener extends ContextLoaderListener {

   
    private static ContainerInitializer containerInitializer = ContainerInitializer.getInstance();

   

    @Override

    public void contextInitialized(final ServletContextEvent event) {

        containerInitializer.startContainer(new AbstractInitializeCallbackAdapter() {

           

            @Override

            public ApplicationContext doInitialize() {

                ServletContext servletContext = event.getServletContext();

                servletContext.setInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, SpringContainer.CONFIG_FILE);

                WebContextLoaderListener.super.contextInitialized(event);

                return WebApplicationContextUtils.getWebApplicationContext(servletContext);

            }

        });

    }

}

ContainerInitializer在初始化时,初始化了参数加载器,读取了日志级别。

2,因为WebContextLoaderListener继承了Spring的ContextLoaderListener类,ContextLoaderListener类是在容器启动时加载spring配置信息用的,web容器启动时,会加载contextInitialized()方法,在这里加载的就是WebContextLoaderListener类的contextInitialized()方法。

3,contextInitialized()方法调用了ContainerInitializer的startContainer()方法,参数是一个AbstractInitializeCallbackAdapter类,先看一下这个AbstractInitializeCallbackAdapter类:

package com.dangdang.ddframe.container.spring.initialize;

public abstract class AbstractInitializeCallbackAdapter implements InitializeCallback {

   

    @Override

    public void logSuccess(final Logger log) {

        log.info("--- dd-frame container started success. ---");

    }

   

    @Override

    public void logFailure(final Logger log, final SystemException cause) {

        log.error("--- dd-frame init failure. ---", cause);

    }

}

其实就是定义了启动成功时候的日志和启动失败时候的日志,他实现的接口InitializeCallback定义了这两个方法,还有启动容器的doInitialize()方法。

4,现在开始看ContainerInitializer的startContainer()方法:

package com.dangdang.ddframe.container.spring.initialize;

    public void startContainer(final InitializeCallback initializeCallback) {

        prepareSystemProperties();

        ApplicationContext context = null;

        try {

            context = initializeCallback.doInitialize();

        // CHECKSTYLE:OFF

        } catch (final Exception ex) {

        // CHECKSTYLE:ON

            logAndThrow(initializeCallback, ex);

        }

        for (Entry<String, InitializeValidator> entry : context.getBeansOfType(InitializeValidator.class).entrySet()) {

            try {

                entry.getValue().validate();

            } catch (final InitializeValidatorException ex) {

                logAndThrow(initializeCallback, ex);

            }

        }

        ContextHolder.initInstance(context);

        initializeCallback.logSuccess(LOG);

       LOG_CONFIG.setLevel(CLASSPATH_PROP_LOADER.getClasspathProperties().getProperty(LogConfig.RUNNING_LOG_LEVEL_KEY));

}

 

5,prepareSystemProperties();方法是把配置文件中的配置项放到java.lang.System中。

然后调用了InitializeCallback的doInitialize()方法,就是在WebContextLoaderListener之中写的那一部分:

@Override

public ApplicationContext doInitialize() {

    ServletContext servletContext = event.getServletContext();

    servletContext.setInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,SpringContainer.CONFIG_FILE);

    WebContextLoaderListener.super.contextInitialized(event);

    return WebApplicationContextUtils.getWebApplicationContext(servletContext);

}

首先设置了配置文件路径:把ContextLoader.CONFIG_LOCATION_PARAM设置为SpringContainer.CONFIG_FILE,这个路径是可配置的,这里配置为:”classpath:META-INF/dd-frame/spring/root/applicationContext.xml”。

然后,在这个方法中调用了父类的contextInitialized()方法,也就是org.springframework.web.context .ContextLoaderListener的contextInitialized()方法:

public void contextInitialized(ServletContextEvent event) {

         initWebApplicationContext(event.getServletContext());

}

6,其中调用的initWebApplicationContext()方法在他的父类org.springframework.web.context.ContextLoader中:

package org.springframework.web.context;

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!");

                   }



                   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.

                            if (this.context == null) {

                                     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);

                                               }

                                               configureAndRefreshWebApplicationContext(cwac, 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");

                            }



                            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;

                   }

         }

7,这个方法就是把spring配置文件中的内容加载到上下文的方法,初始化时this.context为null,调用createWebApplicationContext()方法:

         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() + "]");

                   }

                   return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

         }

8,方法的第一行调用determineContextClass()方法,返回了ConfigurableWebApplicationContext的Class类,这个方法有很多层,看到最后是调用了ContextLoader.properties配置文件,这个配置文件在Spring的包中是和ContextLoader.class文件放在一起的,没有开放给用户使用,不可配置,这个配置文件内容如下:

# Default WebApplicationContext implementation class for ContextLoader.

# Used as fallback when no explicit context implementation has been specified as context-param.

# Not meant to be customized by application developers.



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

如果去掉注释,就一行,代表这里spring的上下文实际上默认是用XmlWebApplicationContext来初始化的。注释里也写了,没打算开放给开发人员去配置。

9,回头看步骤7,在得到Class类后,最后一行调用BeanUtils.instantiateClass()方法进行初始化,并返回。

10,继续看步骤6中的代码,在刚才调用createWebApplicationContext()方法得到对象后,调用configureAndRefreshWebApplicationContext()方法来加载上下文配置:
 

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);
                   if (idParam != null) {
                            wac.setId(idParam);
                   }
                   else {
                            // Generate default id...
                            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                                               ObjectUtils.getDisplayString(sc.getContextPath()));
                   }
          }

          wac.setServletContext(sc);
          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);
          wac.refresh();
}

11,代码最后的refresh()方法,实际调用的是之前初始化的XmlWebApplicationContext的refresh()方法,这个方法的实现在他的祖辈中,这个类的继承关系是:

org.springframework.web.context.support.XmlWebApplicationContext

继承

org.springframework.web.context.support.AbstractRefreshableWebApplicationContext

继承

org.springframework.context.support.AbstractRefreshableConfigApplicationContext

继承

org.springframework.context.support.AbstractRefreshableApplicationContext

继承

org.springframework.context.support.AbstractApplicationContext

refresh()方法在AbstractApplicationContext类中:

public void refresh() throws BeansException, IllegalStateException {
          synchronized (this.startupShutdownMonitor) {
                   // Prepare this context for refreshing.
                   prepareRefresh();

                   // Tell the subclass to refresh the internal bean factory.
                   ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

                   // Prepare the bean factory for use in this context.
                   prepareBeanFactory(beanFactory);

                   try {
                            // Allows post-processing of the bean factory in context subclasses.
                            postProcessBeanFactory(beanFactory);

                            // Invoke factory processors registered as beans in the context.
                            invokeBeanFactoryPostProcessors(beanFactory);

                            // Register bean processors that intercept bean creation.
                            registerBeanPostProcessors(beanFactory);

                            // Initialize message source for this context.
                            initMessageSource();

                            // Initialize event multicaster for this context.
                            initApplicationEventMulticaster();

                            // Initialize other special beans in specific context subclasses.
                            onRefresh();

                            // Check for listener beans and register them.
                            registerListeners();

                            // Instantiate all remaining (non-lazy-init) singletons.
                            finishBeanFactoryInitialization(beanFactory);

                            // Last step: publish corresponding event.
                            finishRefresh();
                   }

                   catch (BeansException ex) {
                            logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);

                            // Destroy already created singletons to avoid dangling resources.
                            destroyBeans();

                            // Reset 'active' flag.
                            cancelRefresh(ex);

                            // Propagate exception to caller.
                            throw ex;
                   }
          }
}

可以看到refresh()方法初始化了一个BeanFactory,这个方法进行了一系列对BeanFactory的初始化和装配工作,每个子方法里都进行了大量的操作,直到finishBeanFactoryInitialization()方法,初始化了所有非懒加载的单例bean的实例。

 

至此spring的上下文加载完成。

 

 

 

 

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/lkforce/article/details/81781031
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞