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

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

前言:

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的上下文加载完成。


来源:http://ddrv.cn

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring的ContextLoaderListener加载上下文的源码分析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏