Spring源码(1)Context篇之ContextLoader

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

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

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

Spring项目如此强大 , 以至于现在的项目都是依赖Spring搭建,天天与spring打交道,自问一下,你是否真的了解它?

Servlet与Spring的关系

J2EE标准 Servlet Spring

我们经常在web.xml里配置如下代码:

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

来查看一下ContextLoaderListener的源码:


    package org.springframework.web.context;

    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;

    /** * * @author Juergen Hoeller * @author Chris Beams * @since 17.02.2003 * @see #setContextInitializers * @see org.springframework.web.WebApplicationInitializer * @see org.springframework.web.util.Log4jConfigListener */
    public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

        /** * 类构造方法 * @see ContextLoader * @see #ContextLoaderListener(WebApplicationContext) * @see #contextInitialized(ServletContextEvent) * @see #contextDestroyed(ServletContextEvent) */
        public ContextLoaderListener() {
        }

        /** * 类构造方法 * method is invoked on this listener. * @param context the application context to manage * @see #contextInitialized(ServletContextEvent) * @see #contextDestroyed(ServletContextEvent) */
        public ContextLoaderListener(WebApplicationContext context) {
            super(context);
        }

        /** * Initialize the root web application context. */
        @Override
        public void contextInitialized(ServletContextEvent event) {
            initWebApplicationContext(event.getServletContext());
        }

        /** * Close the root web application context. */
        @Override
        public void contextDestroyed(ServletContextEvent event) {
            closeWebApplicationContext(event.getServletContext());
            ContextCleanupListener.cleanupAttributes(event.getServletContext());
        }

    }

从源码可以看出来的信息:

  1. Spring的ContextLoaderListener实现自servlet包的ServletContextListener接口(从这里能看出来spring与servlet的依赖关系)
  2. 从类继承关系 extends ContextLoader类,并在contextInitialized方法调用了initWebApplicationContext方法,说明是通过ContextLoader类来实现spring context的初始化的
  3. 其中context销毁的方法,调用了ContextCleanupListener类,该类同样实现了ServletContextListener接口

分析源码的同时可以关联项目启动日志 对应着分析:

    十二月 25, 2018 1:35:22 下午 org.apache.catalina.core.ApplicationContext log
    信息: Initializing Spring root WebApplicationContext
    [2018-12-25 13:35:22  INFO org.springframework.web.context.ContextLoader:304] Root WebApplicationContext: initialization started
    [2018-12-25 13:35:22  INFO org.springframework.web.context.support.XmlWebApplicationContext:583] Refreshing Root WebApplicationContext: startup date [Tue Dec 25 13:35:22 CST 2018]; root of context hierarchy
    [2018-12-25 13:35:22  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext-cxf.xml]
    [2018-12-25 13:35:23  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from class path resource [META-INF/cxf/cxf.xml]
    [2018-12-25 13:35:23  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from class path resource [META-INF/cxf/cxf-servlet.xml]
    [2018-12-25 13:35:23  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\cxf-beans-demo.xml]
    [2018-12-25 13:35:24  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext-ext.xml]
    [2018-12-25 13:35:24  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext-msg.xml]
    [2018-12-25 13:35:24  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext-redis.xml]
    [2018-12-25 13:35:24  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext-websocket.xml]
    [2018-12-25 13:35:24  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext.xml]
    [2018-12-25 13:35:26  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\cxf-beans-demo.xml]
    [2018-12-25 13:35:26  INFO com.ctrip.framework.vi.component.ComponentManager:64] vi.appinfo register self to jmx
    [2018-12-25 13:35:26  INFO com.ctrip.framework.vi.component.ComponentManager:75] vi.appinfo finish register!
    [2018-12-25 13:35:28  WARN org.springframework.beans.factory.support.DefaultListableBeanFactory:1524] Bean creation exception on non-lazy FactoryBean type check: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext.xml]: Invocation of init method failed; nested exception is javax.naming.NameNotFoundException: Name [${db.jndiName}] is not bound in this Context. Unable to find [${db.jndiName}].
    [2018-12-25 13:35:28  WARN org.springframework.beans.GenericTypeAwarePropertyDescriptor:135] Invalid JavaBean property 'ignoreResourceNotFound' being accessed! Ambiguous write methods found next to actually used [public void org.springframework.core.io.support.PropertiesLoaderSupport.setIgnoreResourceNotFound(boolean)]: [public void qunar.tc.qconfig.client.spring.QConfigPropertyPlaceholderConfigurer.setIgnoreResourceNotFound(boolean)]
    [2018-12-25 13:35:28  INFO qunar.tc.qconfig.client.impl.FileStore:237] use remote file, name=config.properties, version=VersionProfile{version=1, profile='fat:'}
    [2018-12-25 13:35:29  INFO org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor:155] JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
    [2018-12-25 13:35:29  INFO org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:325] Bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0' of type [org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
    [2018-12-25 13:35:30  WARN org.springframework.beans.factory.support.DefaultListableBeanFactory:1524] Bean creation exception on non-lazy FactoryBean type check: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sendRetrieveMapper' defined in file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\com\ctrip\groupfinance\hap\account\mapper\SendRetrieveMapper.class]: Cannot resolve reference to bean 'sqlSessionFactory' while setting bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Property 'dataSource' is required
    [2018-12-25 13:35:30  WARN org.springframework.beans.factory.support.DefaultListableBeanFactory:1524] Bean creation exception on non-lazy FactoryBean type check: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userMapper' defined in file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\com\ctrip\groupfinance\hap\account\mapper\UserMapper.class]: Cannot resolve reference to bean 'sqlSessionFactory' while setting bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Property 'dataSource' is required
    [2018-12-25 13:35:30  WARN org.springframework.beans.factory.support.DefaultListableBeanFactory:1524] Bean creation exception ...
    ...
    [2018-12-25 13:37:14  INFO org.springframework.web.context.ContextLoader:344] Root WebApplicationContext: initialization completed in 112076 ms

查看Spirng中ContextLoader的源码,其中initWebApplicationContext()方法即为初始化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;
      }
    }

从上面源码可以看出,正好与打印的日志相对应!!!

有时候项目里需要自定义一个ContextListener,来实现一些系统级别的操作!

    package com.zhoufy.core.web;

    import javax.servlet.ServletContext;
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import javax.servlet.annotation.WebListener;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;

    import com.zhoufy.core.ConfigBean;
    import com.zhoufy.core.ConfigBeanFactory;

    /** * @author zhoufy * @date 2018年12月25日 下午6:57:54 */
    @WebListener
    public class MyServletContextListener implements ServletContextListener {

      private static final Logger logger = LoggerFactory.getLogger(MyServletContextListener.class);

      @Override
      public void contextInitialized(ServletContextEvent event) {
        ServletContext context = event.getServletContext();
        context.log("Initializing MyServletContextListener");

        WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(context);
        ConfigBean config = ctx.getBean(ConfigBean.class);
        ConfigBeanFactory.getInstance().setConfig(config);
        context.setAttribute("webPath", "/");
        context.setAttribute("resPath", "/res");
        context.setAttribute("cssPath", "/res/css");
        context.setAttribute("jsPath", "/res/js");
      }

      @Override
      public void contextDestroyed(ServletContextEvent arg0) {
         logger.info("ServletContex contextDestroyed");
      }
    }

web.xml文件里同样需要添加相关配置:

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!--这里的顺序不能反了,因为MyServletContextListener里用的WebApplicationContext是上面的ContextLoaderListener初始化的-->
    <listener>
        <listener-class>com.zhoufy.core.web.MyServletContextListener </listener-class>
    </listener>

来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring源码(1)Context篇之ContextLoader

评论 抢沙发

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

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

支付宝扫一扫打赏

微信扫一扫打赏