热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:Spring源码跟踪之ContextLoaderListener

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Spring源码跟踪之ContextLoaderListener相关的知识,希望对你有一定的参考价值。 &nb

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Spring源码跟踪之ContextLoaderListener相关的知识,希望对你有一定的参考价值。


  首先我们来看一段web.xml中的配置:


contextConfigLocation
classpath:/applicationContext.xml


org.springframework.web.context.ContextLoaderListener

  有经验的都知道上面一段配置是关于web集成Spring的,每个初次学习Spring的,心中都会疑问为何如此配置,下面跟着我来理解为何如此配置。

       首先理解一下什么是listener?

       Listener 的作用非常类似于load-on-startup Servlet。用于在Web 应用启动时,启动某些后台程序,这些后台程序负责为系统运行提供支持。

        Listener 与load-on-startup Servlet 的区别在于: Listener 的启动时机比load-on-startup Servlet 早,只是Listener 是Servlet 2.3 规范之后才出现的。
使用Listener 只需要两个步骤:
(1)创建Listener 实现类。
(2)在web.xml 文件中配置Listener。
        创建Listener 类必须实现ServletContextListener 接口,该接口包含两个方法。
contextInitialized(ServletContextEvent sce): 启动Web 应用时,系统调用该Filter的方法。

contextDestroyed(ServletContextEvent sce): 关闭Web 应用时候,系统调用Filter的方法。

先大致介绍一下ContextLoaderListener启动运行过程:

简单介绍一下上图的运行流程:

①启动项目时触发contextInitialized方法,该方法就做一件事:通过父类contextLoader的initWebApplicationContext方法创建Spring上下文对象。

②initWebApplicationContext方法做了三件事:创建WebApplicationContext;加载对应的Spring文件创建里面的Bean实例;将WebApplicationContext放入ServletContext(就是Java Web的全局变量)中。

③createWebApplicationContext创建上下文对象,支持用户自定义的上下文对象,但必须继承自ConfigurableWebApplicationContext,而Spring MVC默认使用ConfigurableWebApplicationContext作为ApplicationContext(它仅仅是一个接口)的实现。

④configureAndRefreshWebApplicationContext方法用于封装ApplicationContext数据并且初始化所有相关Bean对象。它会从web.xml中读取名为contextConfigLocation的配置,这就是spring xml数据源设置,然后放到ApplicationContext中,最后调用传说中的refresh方法执行所有Java对象的创建。

⑤完成ApplicationContext创建之后就是将其放入ServletContext中,注意它存储的key值常量。

 下面来看一下ContextLoaderListener的源码:

public class ContextLoaderListener extends ContextLoader
    implements ServletContextListener{
    public ContextLoaderListener()
    {
    }
    public ContextLoaderListener(WebApplicationContext context)
    {
        super(context);
    }
    public void contextInitialized(ServletContextEvent event)
    {
    //这个方法已经废弃,返回值为null
        contextLoader = createContextLoader();
        if(contextLoader == null)
        //因为ContextLoaderListerner继承了ContextLoader,所以可把自身赋值给
            contextLoader = this;
        //接着就实例化webApplicationContext,由父类ContextLoader实现
        contextLoader.initWebApplicationContext(event.getServletContext());
    }
    protected ContextLoader createContextLoader()
    {
        return null;
    }
    public ContextLoader getContextLoader()
    {
        return contextLoader;
    }
    public void contextDestroyed(ServletContextEvent event)
    {
        if(contextLoader != null)
            contextLoader.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
    private ContextLoader contextLoader;
}

因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成,ContextLoader是一个工具类,用来初始化WebApplicationContext,其主要方法就是initWebApplicationContext,我们继续追踪initWebApplicationContext这个方法,我们发现,原来ContextLoader是把WebApplicationContext(XmlWebApplicationContext是默认实现类)放在了ServletContext中,ServletContext也是一个“容器”,也是一个类似Map的结构,而WebApplicationContext在ServletContext中的KEY就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,我们如果要使用WebApplicationContext则需要从ServletContext取出,Spring提供了一个WebApplicationContextUtils类,可以方便的取出WebApplicationContext,只要把ServletContext传入就可以了。 

在创建ContextLoader,会执行它的一段静态代码块。

  static 
    {
        try
        {
         // 这里创建一个ClassPathResource对象,载入ContextLoader.properties,用于创建对应的ApplicationContext容器  
        // 这个文件跟ContextLoader类在同一个目录下,文件内容如:  
        // org.springframework.web.context.WebApplicatiOnContext=org.springframework.web.context.support.XmlWebApplicationContext  
        // 如此说来,spring默认初始化的是XmlWebApplicationContext  
            ClassPathResource resource = new ClassPathResource("ContextLoader.properties", org/springframework/web/context/ContextLoader);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch(IOException ex)
        {
            throw new IllegalStateException((new StringBuilder("Could not load ‘ContextLoader.properties‘: ")).append(ex.getMessage()).toString());
        }
    }

下面我们主要看看ContextLoader这个类的initWebApplicationContext方法。

        //以下这段代码主要判断是否重复实例化的问题,因为实例化webApplicationContext后,会把它放到servletContext的一个属性里,所以我们可以从servletContext的属性取出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!");

这个地点是为什么Spring需要引入common-logging这个jar,不然会报错。

 logger = LogFactory.getLog(org/springframework/web/context/ContextLoader);

调用LogFactory.getLog()时发生的事情:

          (1).common-logging首先在CLASSPATH中查找commons-logging.properties文件。这个属性文件至少定义org.apache.commons.logging.Log属性,它的值应该是上述任意Log接口实现的完整限定名称。如果找到 org.apache.commons.logging.Log属相,则使用该属相对应的日志组件。结束发现过程。

        (2).如果上面的步骤失败(文件不存在或属相不存在),common-logging接着检查系统属性org.apache.commons.logging.Log。如果找到org.apache.commons.logging.Log系统属性,则使用该系统属性对应的日志组件。结束发现过程。
        (3).如果找不到org.apache.commons.logging.Log系统属性,common-logging接着在CLASSPATH中寻找log4j的类。如果找到了就假定应用要使用的是log4j。不过这时log4j本身的属性仍要通过log4j.properties文件正确配置。结束发现过程。
        (4).如果上述查找均不能找到适当的Logging API,但应用程序正运行在JRE 1.4或更高版本上,则默认使用JRE 1.4的日志记录功能。结束发现过程。
        (5).最后,如果上述操作都失败(JRE 版本也低于1.4),则应用将使用内建的SimpleLog。SimpleLog把所有日志信息直接输出到System.err。结束发现过程。

        为了简化配置 commons-logging ,一般不使用 commons-logging 的配置文件,也不设置与 commons-logging 相关的系统环境变量,而只需将 Log4j 的 Jar 包放置到 classpash 中就可以了。这样就很简单地完成了 commons-logging 与 Log4j 的融合。

接着我们可看到:

 //接着就会创建webApplicationContext
 if(context == null)
 context = createWebApplicationContext(servletContext);

跟进createWebApplicationContext这个方法:

 protected WebApplicationContext createWebApplicationContext(ServletContext sc)
    {
    //获取相应的class,其实就是ConfigurableWebApplicationContext.class
        Class contextClass = determineContextClass(sc);
        //判断是否为 ConfigurableWebApplicationContext.class或从它继承
        if(!org/springframework/web/context/ConfigurableWebApplicationContext.isAssignableFrom(contextClass))
        {
        //若非ConfigurableWebApplicationContext.class或非其子类则抛出异常
            throw new ApplicationContextException((new StringBuilder("Custom context class [")).append(contextClass.getName()).append("] is not of type [").append(org/springframework/web/context/ConfigurableWebApplicationContext.getName()).append("]").toString());
        } else
        {
          //创建一个contextClass的实例对象返回,然后就是把context强制转换为configrableWebApplicationContext,实例化spring容器
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            return wac;
        }
    }

 该方法首先判断从web.xml文件的初始化参数CONTEXT_CLASS_PARAM(的定义为public static final String CONTEXT_CLASS_PARAM = "contextClass";)获取的的类名是否存在,如果存在,则容器的Class;否则返回默认的Class。如何获取默认的容器Class,注意看创建contextLoader时的代码注释就知道了,  由此看来,spring不仅有默认的applicationContext的容器类,还允许我们自定义applicationContext容器类,不过Spring不建义我们自定义applicationContext容器类。

 protected Class determineContextClass(ServletContext servletContext)
    {
        String contextClassName;
        //从servletContext读取contextClassName
        contextClassName = servletContext.getInitParameter("contextClass");
        if(contextClassName == null)
        //否则创建默认的容器的Class对象,即:org.springframework.web.context.support.XmlWebApplicationContext 
 contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
         //如果从servletContext读取到的contextClassName不为空,就返回对应的class类
        return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        ClassNotFoundException ex;
        ex;
        //如果找不到该类名的类就抛出异常
        throw new ApplicationContextException((new StringBuilder("Failed to load custom context class [")).append(contextClassName).append("]").toString(), ex);
        contextClassName = defaultStrategies.getProperty(org/springframework/web/context/WebApplicationContext.getName());
        return ClassUtils.forName(contextClassName, org/springframework/web/context/ContextLoader.getClassLoader());
        ex;
        throw new ApplicationContextException((new StringBuilder("Failed to load default context class [")).append(contextClassName).append("]").toString(), ex);
    }

接着看核心方法configureAndRefreshWebApplicationContext

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)
    {
        if(ObjectUtils.identityToString(wac).equals(wac.getId()))
        {
            String idParam = sc.getInitParameter("contextId");
            if(idParam != null)
                wac.setId(idParam);
            else
            if(sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5)
                wac.setId((new StringBuilder(String.valueOf(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX))).append(ObjectUtils.getDisplayString(sc.getServletContextName())).toString());
            else
                wac.setId((new StringBuilder(String.valueOf(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX))).append(ObjectUtils.getDisplayString(sc.getContextPath())).toString());
        }
        ApplicationContext parent = loadParentContext(sc);
        wac.setParent(parent);
        //把servletContext放到webApplicationContext中,以后可以直接取出来用
        wac.setServletContext(sc);
        String initParameter = sc.getInitParameter("contextConfigLocation");
        if(initParameter != null)
            wac.setConfigLocation(initParameter);
         //用户自己的一些设置
        customizeContext(sc, wac);
        //进行bean加载
        wac.refresh();
    }

我们进入webApplicationContext的refresh方法看一下

   public void refresh()
        throws BeansException, IllegalStateException
    {
        synchronized(startupShutdownMonitor)
        {
            prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            prepareBeanFactory(beanFactory);
            try
            {
                postProcessBeanFactory(beanFactory);
                invokeBeanFactoryPostProcessors(beanFactory);
                registerBeanPostProcessors(beanFactory);
                initMessageSource();
                initApplicationEventMulticaster();
                onRefresh();
                registerListeners();
                finishBeanFactoryInitialization(beanFactory);
                finishRefresh();
            }
            catch(BeansException ex)
            {
                destroyBeans();
                cancelRefresh(ex);
                throw ex;
            }
        }
    }

这是一个同步的方法,这里最核心的方法就是obtainFreshBeanFactory方法。其他的方法都是对webApplicationContext和beanFactory做一些前后的装饰和准备。

 protected ConfigurableListableBeanFactory obtainFreshBeanFactory()
    {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if(logger.isDebugEnabled())
            logger.debug((new StringBuilder("Bean factory for ")).append(getDisplayName()).append(": ").append(beanFactory).toString());
        return beanFactory;
    }

这里的核心方法就是refreshBeanFactory();它负责生成BeanFactory并加载bean

protected final void refreshBeanFactory()
        throws BeansException
    {
    //判断是否已经存在beanFactory如果有则销毁。
        if(hasBeanFactory())
        {
            destroyBeans();
            closeBeanFactory();
        }
        try
        {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);//用户自己做一些设置
            loadBeanDefinitions(beanFactory);//这个方法很关键,负责加载所有的bean
            synchronized(beanFactoryMonitor)
            {
                this.beanFactory = beanFactory;
            }
        }
        catch(IOException ex)
        {
            throw new ApplicationContextException((new StringBuilder("I/O error parsing bean definition source for ")).append(getDisplayName()).toString(), ex);
        }
    }

然后我们进入loadBeanDefinitions(beanFactory);看一下

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
        throws BeansException, IOException
    {
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        beanDefinitionReader.setEnvironment(getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
        initBeanDefinitionReader(beanDefinitionReader);
        loadBeanDefinitions(beanDefinitionReader);
    }

这里主要工作就是new一个XmlBeanDefinitionReader,给它设置environment,resourceLoader和entityResolver,注意一下由于webApplicationContext实现了ResouceLoader接口,所以它本身就是一个ResourceLoader.我们可以看到它并不自己去实现lobeanDefinitions方法,而是委托给XmlBeanDefinitionReader去实现。

 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader)
        throws IOException
    {
        String configLocations[] = getConfigLocations();
        if(configLocations != null)
        {
            String as[];
            int j = (as = configLocations).length;
            for(int i = 0; i < j; i++)
            {
                String configLocation = as[i];
                reader.loadBeanDefinitions(configLocation);
            }
        }
    }

这里就对configLocations进行bean的加载,调用重载的方法

  public int loadBeanDefinitions(String location)
        throws BeanDefinitionStoreException
    {
        return loadBeanDefinitions(location, null);
    }



本文出自 “进击的程序猿” 博客,请务必保留此出处http://zangyanan.blog.51cto.com/11610700/1853657


推荐阅读
  • 在前文探讨了Spring如何为特定的bean选择合适的通知器后,本文将进一步深入分析Spring AOP框架中代理对象的生成机制。具体而言,我们将详细解析如何通过代理技术将通知器(Advisor)中包含的通知(Advice)应用到目标bean上,以实现切面编程的核心功能。 ... [详细]
  • Android中将独立SO库封装进JAR包并实现SO库的加载与调用
    在Android开发中,将独立的SO库封装进JAR包并实现其加载与调用是一个常见的需求。本文详细介绍了如何将SO库嵌入到JAR包中,并确保在外部应用调用该JAR包时能够正确加载和使用这些SO库。通过这种方式,开发者可以更方便地管理和分发包含原生代码的库文件,提高开发效率和代码复用性。文章还探讨了常见的问题及其解决方案,帮助开发者避免在实际应用中遇到的坑。 ... [详细]
  • 在使用SSH框架进行项目开发时,经常会遇到一些常见的问题。例如,在Spring配置文件中配置AOP事务声明后,进行单元测试时可能会出现“No Hibernate Session bound to thread”的错误。本文将详细探讨这一问题的原因,并提供有效的解决方案,帮助开发者顺利解决此类问题。 ... [详细]
  • 在处理遗留数据库的映射时,反向工程是一个重要的初始步骤。由于实体模式已经在数据库系统中存在,Hibernate 提供了自动化工具来简化这一过程,帮助开发人员快速生成持久化类和映射文件。通过反向工程,可以显著提高开发效率并减少手动配置的错误。此外,该工具还支持对现有数据库结构进行分析,自动生成符合 Hibernate 规范的配置文件,从而加速项目的启动和开发周期。 ... [详细]
  • 在开发过程中,我最初也依赖于功能全面但操作繁琐的集成开发环境(IDE),如Borland Delphi 和 Microsoft Visual Studio。然而,随着对高效开发的追求,我逐渐转向了更加轻量级和灵活的工具组合。通过 CLIfe,我构建了一个高度定制化的开发环境,不仅提高了代码编写效率,还简化了项目管理流程。这一配置结合了多种强大的命令行工具和插件,使我在日常开发中能够更加得心应手。 ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 在Kohana 3框架中,实现最优的即时消息显示方法是许多开发者关注的问题。本文将探讨如何高效、优雅地展示flash消息,包括最佳实践和技术细节,以提升用户体验和代码可维护性。 ... [详细]
  • 本文详细探讨了Java事件处理机制的核心概念与实现原理,内容浅显易懂,适合初学者逐步掌握。通过具体的示例和详细的解释,读者可以深入了解Java事件模型的工作方式及其在实际开发中的应用。 ... [详细]
  • 深入理解 Java 控制结构的全面指南 ... [详细]
  • JavaScript XML操作实用工具类:XmlUtilsJS技巧与应用 ... [详细]
  • 深入解析:React与Webpack配置进阶指南(第二部分)
    在本篇进阶指南的第二部分中,我们将继续探讨 React 与 Webpack 的高级配置技巧。通过实际案例,我们将展示如何使用 React 和 Webpack 构建一个简单的 Todo 应用程序,具体包括 `TodoApp.js` 文件中的代码实现,如导入 React 和自定义组件 `TodoList`。此外,我们还将深入讲解 Webpack 配置文件的优化方法,以提升开发效率和应用性能。 ... [详细]
  • 在腾讯云服务器上部署Nginx的详细指南中,首先需要确保安装必要的依赖包。如果这些依赖包已安装,可直接跳过此步骤。具体命令包括 `yum -y install gcc gcc-c++ wget net-tools pcre-devel zlib-devel`。接下来,本文将详细介绍如何下载、编译和配置Nginx,以确保其在腾讯云服务器上顺利运行。此外,还将提供一些优化建议,帮助用户提升Nginx的性能和安全性。 ... [详细]
  • Netty框架中运用Protobuf实现高效通信协议
    在Netty框架中,通过引入Protobuf来实现高效的通信协议。为了使用Protobuf,需要先准备好环境,包括下载并安装Protobuf的代码生成器`protoc`以及相应的源码包。具体资源可从官方下载页面获取,确保版本兼容性以充分发挥其性能优势。此外,配置好开发环境后,可以通过定义`.proto`文件来自动生成Java类,从而简化数据序列化和反序列化的操作,提高通信效率。 ... [详细]
  • 在 CentOS 6.6 系统中搭建 MONO 和 Jexus 以支持 ASP.NET 及 MVC 应用的运行环境配置指南
    本文提供了在 CentOS 6.6 系统上配置 MONO 和 Jexus 以支持 ASP.NET 及 MVC 应用的详细步骤。首先,确保本机环境为 CentOS 6.6,并使用阿里云的 YUM 源来安装必要的软件包,包括 gcc、gcc-c++、bison、pkgconfig 和 glib2-devel。这些软件包是构建和运行 MONO 环境的基础,确保系统能够顺利支持 ASP.NET 和 MVC 应用的部署和运行。 ... [详细]
  • 本文作为探讨PHP依赖注入容器系列文章的开篇,将首先通过具体示例详细阐述依赖注入的基本概念及其重要性,为后续深入解析容器的实现奠定基础。 ... [详细]
author-avatar
小庄2502921871
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有