SPRING 3.2.X 源代码分析(转)
转自:http://www.javastar.org/?tag=%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90
最近在看spring框架的源代码,随手写点代码的分析记录,方便以后查阅,也和大家交流一下,欢迎提出问题和建议。
spring是一个功能比较丰富的框架,所以,也不是一篇两篇文章可以说清楚的,所以,这里先占位,做一个所有文章的目录。
SPING 3.2.X 源代码分析之一: SPRING框架的由来发展及核心功能
最近断断续续的看了几天的spring框架的源代码了,突然想把一些东西记录下来,所以,随手就写点东西吧,有些借鉴了网上的一些文章,在此表示感谢。
要学好一个东西,最好先了解一下它的由来和发展情况,这样子就可以把握其是因为什么出现的,为了解决什么样的问题,为什么发展到了现在的这个样子,然后再去研究它,就会有比较好的目标,也更容易理解它。
所以我们先来看看spring的由来和发展,及其提供的核心功能。
1、spring框架的由来
Spring是java平台上的一个开源应用框架。它的第一个版本是由Rod Johnson写出来的。Rod在他的Expert One-On-One Java EE Design and Development(Java企业应用设计与开发的专家一对一)一书中首次发布了这个框架。
目前,该框架也可以移植到.NET的环境下。
Spring的框架首次在2003年6月的Apache 2.0的使用许可中发布。第一个具有里程碑意义的版本是2004年3月发布的1.0。2004年9月和2005年3月先后又有重要的版本面世。
Spring框架本身并没有强制实行任何特别的编程模式。在Java社区里,Spring作为EJB模型之外的另外一个选择甚至是替代品而广为流行。从设计上看,Spring给予了Java程序员许多的自由度,但同时对业界常见的问题也提供了良好的文档和易于使用的方法。
Spring框架的核心功能在任何Java应用中都是适用的。在基于Java企业平台上的web应用中,大量的拓展和改进得以形成。为此,Spring获得了广泛的欢迎,并被许多公司认可为具有战略意义的重要框架。
2. Spring框架的发展历史
Spring框架最开始的部分是由Rod Johnson于2000年为伦敦的金融界提供独立咨询业务时写出来的。在《Java企业应用设计与开发的专家一对一》一书中,Rod进一步拓展了他的代码,以阐述“如何让应用程序能以超出当时大众所惯于接受的易用性和稳定性与J2EE平台上的不同组件合作”的观点。
在2001年,web应用的主流编程模式为Java Servlet API和EJB。两者都是由太阳微系统公司与其他一些开发商和利益团体提出的,并在Java业界里获得了广泛的共识。那些非Web的应用,比如用户端的或批处理的应用,也可以基于能够提供所需功能的开源或商用工具和项目。
基于最优方法并适用于各种应用类型的Spring框架的建立要归功于Rod Johnson。这些想法也在他的书中得以阐述。书发表后,基于读者的要求,源代码在开源使用协议下得以提供。
一批自愿拓展Spring框架的程序开发员组成了团队,2003年2月在Sourceforge上构建了一个项目。在Spring框架上工作了一年之后,这个团队在2004年3月发布了第一个版本(1.0)。这个版本之后,Spring框架在Java社区里变得异常流行,部分的要归结于它好于一般水准的文档功能和参考文献,特别是对于一个开源项目而言尤其如此。
但是,Spring框架在2004年也备受批评,有时它也成为热烈争论的主题。Spring的第一个版本发布时,许多程序员和领先的设计人员把它看作是远离传统编程模式的一步;特别是对于EJB而言尤其如此。Spring框架的一个重要设计目标就是更容易地与已有的J2EE标准和商用工具整合。在很大程度上,这个目标使得通过受争议的官方委员会控制的规范文档来定义功能变得可有可无。
Spring框架使之前并不受欢迎的技术在短时间内迅速走红,最有名的例子就是反向控制(IOC)。2004年,Spring框架的采用率非常之高;通过推出自身的AOP(面向方向的编程),Spring使AOP整体而言在Java社区里广受欢迎。
2005年,Spring因具有里程碑意义的新的版本的推出,更多功能的添加,从而得到了比2004年更高的采用率。2004年底创建的Spring论坛也对框架的推广而推波助澜。论坛对广大用户而言已经成为最重要的信息和帮助的源泉。
2005年,Spring框架的开发人员成立了自己的公司,来提供对Spring的商业支持,其中最显著的就是与BEA的合作。2005年12月,第一个Spring会议在迈阿密举行,3天的课程吸引了300名开发人员。2006年6月在安特卫普召开的会议有400多名开发人员。
在spring 2.5之前的版本,spring和ejb是绝对竞争的态势,但是从spring2.5开始,spring开始借鉴和兼容EJB的规范,和EJB的关系也逐步的融洽起来,EJB的很多最新的规范都已经得到了Spring的支持。
3. Spring框架的主要功能
• 基于Java
Beans的配置管理,采用IOC的原理,特别是对依赖注射技术的使用。这些都用来减少各组件间对实施细则的相互依赖性。
• 一个核心的,全局适用的bean工厂
• 一个一般抽象化的层面来管理数据库间的数据处理
• 建立在框架内的,对Java数据处理API和单独的JDBC数据源的一般性策略。因此,在数据处理支持上对Java企业版本环境的依赖性得以消除
• 和一些可持续性的框架,如Hibernate,JDO,iBATIS和db4o,的整合
• web应用中的MVC框架,基于核心的Spring功能,支持多种产生视图的技术,包括JSP,FreeMarker,Velocity,Tiles,iText,和POI
• 大量的AOP框架以提供诸如数据处理管理的服务。同IOC的功能一样,目的是提高系统的模块化程度
SPING 3.2.X 源代码分析之二: SPRING源码的包结构
spring框架的由来发展及主要功能在上一篇中我们已经介绍过了,下面我们开始正式的研究分析spring的源代码。
要学习研究spring的源代码,当然要先准备好环境了,请先看下面的两篇文章
Spring 3.2.6(spring 4.0.*)导入Eclipse的方法及常见问题解决
根据上面的两篇文章,我们应该已经下载到了需要的spring源码包,并且已经导入到了eclipse中,可以通过eclipse来阅读代码了。
既然阅读spring的源代码,那么我们就应该先对spring的源代码的整体结构有个了解,今天抽时间整理了一下spring3.2.x的源码包的结构图,如下所示:
spring框架的基础核心功能就是IOC和AOP,从图中可以看出,IOC的实现包spring-beans和AOP的实现包spring-aop也是整个框架的基础,而spring-core是整个框架的核心,基础的功能都在这里。
要真正的把IOC和AOP的模式贯彻近各个功能,当然就缺不了一个上下文环境,所以,这里也可以看出其他的附加功能都要通过spring-context包进行粘合,没有这个context,所有的信息和功能都是虚无的。
而web部分的功能,都是要依赖spring-web来实现的,从图中可以很直观的看出来。
spring-webmvc包,是spring mvc的实现包。
spring-tx是统一的事务处理包。
spring-orm包,是orm框架mybatis、hibernate3等的粘合包。
我们把握了这些核心包,其他的都可以慢慢的扩展出来。
这里我们先整体认识了一下spring源代码的各个包,后面我们针对不同的功能来进行代码分析。
SPING 3.2.X 源代码分析之三: SPRING源码的整体架构分析
sping 3.2.x 源代码分析的上一篇sping 3.2.x 源代码分析之二: spring源码的包结构中,我们大概了解了spring3.2.x的包结构,并且用一张图的方式,让大家很直观的了解了spring各个包的关系。
spring的系统架构
感觉既然是做源代码的分析,那么只是了解了sping的包结构,还不太够,应该从spring整个框架的设计思想来分析一下spring,这样子,在着手去看具体的代码的时候,才会有个整体的指导思想。
直接上图,下面的三张图都是从spring的官方文档中摘取出来的,分别是spring 3.2.8和spring 4.0.3的架构图,及spring3.2.8的web应用功能结构图,因为spring4.0的应用结构图和3.2.x的差不多,就不放上来了,感兴趣的可以去spring的官方看一下。
spring 3.2.8 的系统架构图
对比我们在上一篇文章中的spring3.2.x源码包结构图,是不是基本上都能对应起来。
最底层的是框架的测试程序,这个算一个质量的保证;框架的核心是beans、core、context和el;在核心层之上是AOP、Aspects和Instrumentation;再向上就是数据库和web的模块。
再来看一下spring4.0.3的系统架构图。
spring 4.0.3 的系统架构图
spring 4.0.x对比spring3.2.x的系统架构变化
从图中可以看出,总体的层次结构没有太大变化,变化的是spring 4.0.3去掉了struts模块(spring-struts包)。现在的spring mvc的确已经足够优秀了,大量的web应用均已经使用了spring mvc。而struts1.x的架构太落后了,struts2.x是struts自身提供了和spring的集成包,但是由于之前版本的struts2存在很多致命的安全漏洞,所以,大大影响了其使用度,好在最新的2.3.16版本的struts安全有所改善,希望不会再出什么大乱子。
web部分去掉了struts模块,但是增加WebSocket模块(spring-websocket包),增加了对WebSocket、SockJS以及STOMP的支持,它与JSR-356Java WebSocket API兼容。另外,还提供了基于SockJS(对WebSocket的模拟)的回调方案,以适应不支持WebSocket协议的浏览器。
同时,增加了messaging模块(spring-messaging),提供了对STOMP的支持,以及用于路由和处理来自WebSocket客户端的STOMP消息的注解编程模型。spring-messaging模块中还 包含了Spring Integration项目中的核心抽象类,如Message、MessageChannel、MessageHandler。
如果去看源代码的话,还可以发现还有一个新增的包,加强了beans模块,就是spring-beans-groovy。应用可以部分或完全使用Groovy编写。借助于Spring 4.0,能够使用Groovy DSL定义外部的Bean配置,这类似于XML Bean声明,但是语法更为简洁。使用Groovy还能够在启动代码中直接嵌入Bean的声明。
有机会大家还是多学学groovy吧,技多不压身,有时间多学习总是好的。
spring的设计理念
我们学习spring的时候就知道,它提供的最大的也是最基本的功能就是DI和IOC,而作用的主要对象就是Bean。Bean 在 Spring 中作用就像 Object 对 OOP 的意义一样,没有对象的概念就像没有面向对象编程,Spring 中没有 Bean 也就没有 Spring 存在的意义。
为什么要 Bean 这种角色 Bean 或者为何在 Spring 如此重要,这由 Spring 框架的设计目标决定,Spring 为何如此流行,我们用 Spring 的原因是什么,想想你会发现原来 Spring 解决了一个非常关键的问题他可以让你把对象之间的依赖关系转而用配置文件来管理,也就是他的依赖注入机制。而这个注入关系在一个叫 Ioc 容器中管理,那 Ioc 容器中有又是什么就是被 Bean 包裹的对象。Spring 正是通过把对象包装在 Bean 中而达到对这些对象管理以及一些列额外操作的目的。
它这种设计策略完全类似于 Java 实现 OOP 的设计理念,当然了 Java 本身的设计要比 Spring 复杂太多太多,但是都是构建一个数据结构,然后根据这个数据结构设计他的生存环境,并让它在这个环境中按照一定的规律在不停的运动,在它们的不停运动中设计一系列与环境或者与其他个体完成信息交换。这样想来回过头想想我们用到的其他框架都是大慨类似的设计理念。
Spring在此基础之上,又将各类应用中的最常用的功能抽象出来,做成一个一个的功能模块,这样子,开发人员就可以少做很多的重复性的基础工作,可以更多的将精力放在业务开发之上。
分析完了spring的系统架构,那么我们来看一下具体的最常见的Web应用中的spring各个模块的层次结构。
spring的web应用架构图
这个图就是反应了一个很常见的web应用的多层架构,不同的模块在不同的层次发挥作用,将各个层次连接起来,就组成了一个实际的应用系统。
spring的架构暂时分析到这里,后续结合具体的spring功能代码,再逐步展开分析。
SPING 3.2.X 源代码分析之四:SPRING核心功能组件及类关系图
我们在前面的文章中已经分析了spring的核心组件主要由Bean 组件、Context 组件、Core 组件、AOP组件组成,在这里我们就简要介绍一下这几个组件的核心类继承关系,了解了这些关系,基本上就了解了spring代码的核心,就可以按照脉络去更深一步的看具体的代码了。
下面我们分别来看看各个组件的核心类关系。
spring Bean 组件
Bean 组件在 Spring 的 org.springframework.beans 包下。
这个包下的所有类主要解决了三件事:Bean 的定义、Bean 的创建以及对 Bean 的解析。对 Spring 的使用者来说唯一需要关心的就是 Bean 的创建,其他两个由 Spring 在内部帮你完成了,对使用者来说是透明的。
Spring Bean 的创建时典型的工厂模式,他的顶级接口是 BeanFactory,下图是这个工厂的继承层次关系:
Bean 工厂的继承关系图
BeanFactory 有三个子类:ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory。但是从上图中我们可以发现最终的默认实现类是 DefaultListableBeanFactory,他实现了所有的接口。那为何要定义这么多层次的接口呢?查阅这些接口的源码和说明发现,每个接口 都有他使用的场合,它主要是为了区分在 Spring 内部在操作过程中对象的传递和转化过程中,对对象的数据访问所做的限制。例如 ListableBeanFactory 接口表示这些 Bean 是可列表的,而 HierarchicalBeanFactory 表示的是这些 Bean 是有继承关系的,也就是每个 Bean 有可能有父 Bean。AutowireCapableBeanFactory 接口定义 Bean 的自动装配规则。这四个接口共同定义了 Bean 的集合、Bean 之间的关系、以及 Bean 行为。
Bean 的定义主要有 BeanDefinition 描述,如下图说明了这些类的层次关系:
Bean 定义的类层次关系图
Bean 的定义就是完整的描述了在 Spring 的配置文件中你定义的 <bean/> 节点中所有的信息,包括各种子节点。当 Spring 成功解析你定义的一个 <bean/> 节点后,在 Spring 的内部他就被转化成 BeanDefinition 对象。以后所有的操作都是对这个对象完成的。
Bean 的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证有足够的灵活性,以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。这个解析过程主要通过下图中的类完成:
Bean 的解析类图
还有对tag 的解析这里没有,可以自己去研究一下代码。
spring Context 组件
Context 在 Spring 的 org.springframework.context 包下。
前面已经讲解了 Context 组件在 Spring 中的作用,他实际上就是给 Spring 提供一个运行时的环境,用以保存各个对象的状态。下面看一下这个环境是如何构建的。
ApplicationContext 是 Context 的顶级父类,他除了能标识一个应用环境的基本信息外,他还继承了五个接口,这五个接口主要是扩展了 Context 的功能。下面是 Context 的类结构图:
Context 相关的类结构图
从上图中可以看出 ApplicationContext 继承了 BeanFactory,这也说明了 Spring 容器中运行的主体对象是 Bean,另外
ApplicationContext 继承了 ResourceLoader 接口,使得 ApplicationContext 可以访问到任何外部资源,这将在 Core 中详细说明。
ApplicationContext 的子类主要包含两个方面:
- ConfigurableApplicationContext 表示该 Context 是可修改的,也就是在构建 Context 中用户可以动态添加或修改已有的配置信息,它下面又有多个子类,其中最经常使用的是可更新的 Context,即 AbstractRefreshableApplicationContext 类。
- WebApplicationContext 顾名思义,就是为 web 准备的 Context 他可以直接访问到 ServletContext,通常情况下,这个接口使用的少。
再往下分就是按照构建 Context 的文件类型,接着就是访问 Context 的方式。这样一级一级构成了完整的 Context 等级层次。
总体来说 ApplicationContext 必须要完成以下几件事:
- 标识一个应用环境
- 利用 BeanFactory 创建 Bean 对象
- 保存对象关系表
- 能够捕获各种事件
Context 作为 Spring 的 Ioc 容器,基本上整合了 Spring 的大部分功能,或者说是大部分功能的基础。
Core 组件
Core 组件作为 Spring 的核心组件,他其中包含了很多的关键类,其中一个重要组成部分就是定义了资源的访问方式。这种把所有资源都抽象成一个接口的方式很值得在以后的设计中拿来学习。下面就重要看一下这个部分在 Spring 的作用。
下图是 Resource 相关的类结构图:
Resource 相关的类结构图
从上图可以看出 Resource 接口封装了各种可能的资源类型,也就是对使用者来说屏蔽了文件类型的不同。对资源的提供者来说,如何把资源包装起来交给其他人用这也是一个问题,我们看到 Resource 接口继承了 InputStreamSource 接口,这个接口中有个 getInputStream 方法,返回的是 InputStream 类。这样所有的资源都被可以通过 InputStream 这个类来获取,所以也屏蔽了资源的提供者。另外还有一个问题就是加载资源的问题,也就是资源的加载者要统一,从上图中可以看出这个任务是由 ResourceLoader 接口完成,他屏蔽了所有的资源加载者的差异,只需要实现这个接口就可以加载所有的资源,他的默认实现是 DefaultResourceLoader。
下面看一下 Context 和 Resource 是如何建立关系的?首先看一下他们的类关系图:
Context 和 Resource 的类关系图
从上图可以看出,Context 是把资源的加载、解析和描述工作委托给了 ResourcePatternResolver 类来完成,他相当于一个接头人,他把资源的加载、解析和资源的定义整合在一起便于其他组件使用。Core 组件中还有很多类似的方式。
Ioc 容器
Ioc 容器实际上就是 Context 组件结合其他两个组件共同构建了一个 Bean 关系网,如何构建这个关系网?构建的入口就在 AbstractApplicationContext 类的 refresh 方法中。
注意 BeanFactory 对象的类型的变化,前面介绍了他有很多子类,在什么情况下使用不同的子类这非常关键。BeanFactory 的原始对象是 DefaultListableBeanFactory,这个非常关键,因为他设计到后面对这个对象的多种操作,下面看一下这个类的继承层次类图:
DefaultListableBeanFactory 类继承关系图
从这个图中发现除了 BeanFactory 相关的类外,还发现了与 Bean 的 register 相关。这在 refreshBeanFactory 方法中有一行 loadBeanDefinitions(beanFactory) 将找到答案,这个方法将开始加载、解析 Bean 的定义,也就是把用户定义的数据结构转化为 Ioc 容器中的特定数据结构。
AOP组件
Spring 的 AOP是使用动态代理的原理实现的,在 Jdk 的 java.lang.reflect 包下有个 Proxy 类,它正是构造代理类的入口。
Spring 的 Aop 实现是遵守 Aop 联盟的约定。同时 Spring 又扩展了它,增加了如 Pointcut、Advisor 等一些接口使得更加灵活。
下面是 Jdk 动态代理的类图:
spring Jdk 动态代理的类图
上图清楚的显示了 Spring 引用了 Aop Alliance 定义的接口,而spring进而扩展出很多的Interceptor,供使用者选择使用。使用者也可以自行扩展拦截器。
Spring 的代理方式有两个 Jdk 动态代理和 CGLIB 代理,这两个代理方式的使用使用了策略模式。看一下
Spring 中策略模式结构图
在上面结构图中与标准的策略模式结构稍微有点不同,这里抽象策略是 AopProxy 接口,Cglib2AopProxy 和 JdkDynamicAopProxy 分别代表两种策略的实现方式,ProxyFactoryBean 就是代表 Context 角色,它根据条件选择使用 Jdk 代理方式还是 CGLIB 方式,而另外三个类主要是来负责创建具体策略对象,ProxyFactoryBean 是通过依赖的方法来关联具体策略对象的,它是通过调用策略对象的 getProxy(ClassLoader classLoader) 方法来完成操作。
本文通过从 Spring 的几个核心组件入手,找出构建 Spring 框架的骨骼架构,分析了 Spring 在设计的一些设计理念,希望可以帮大家先建立起一个对于spring框架的程序的整体的概念,后续将从具体的代码入手进行spring的代码的分析。
本文大部分内容摘自:http://www.ibm.com/developerworks/cn/java/j-lo-spring-principle/
SPING 3.2.X 源代码分析之五:创建SPRING的测试工程
前面的几部分,我们对spring的发展情况,总体的架构、包结构和主要类的关系进行了讲解,大家应该对spring有了一个比较整体的初步认识了。
从本篇开始我们准备正式开始接触源代码。
基本环境介绍
spring框架提供的最基本的功能就是IOC、AOP及以此为基础的一些常用功能的扩展。
IOC和AOP要交付给其他的各种功能使用,起粘合作用的就是spring-context包,要有一个基本的context环境。
所以,我们这里就先从context的加载过程开始认识spring的源代码。
以下分析均以web应用为基础进行,我们这里要用到spring的spring-webmvc包。
创建测试工程
请先安装好jdk和eclipse,并且安装好eclipse的maven插件。
然后创建一个maven的web application,创建好之后,把jdk版本修改成你安装的版本,至少1.6.x以上,如果要导入spring的源代码,则至少需要1.7.x。
可以参考这两篇文章:
Spring 3.2.6(spring 4.0.*)导入Eclipse的方法及常见问题解决
打开你创建的maven工程中的web.xml文件,增加
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
然后创建 /WEB-INF/applicationContext.xml 文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
</beans>
就是一个空的spring的配置文件。
打开pom.xml文件
增加自定义变量
<properties>
<jettyVersion>8.1.12.v20130726</jettyVersion>
</properties>
增加以下的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>3.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jettyVersion}</version>
</dependency>
增加maven的jetty插件
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jettyVersion}</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<webAppConfig>
<contextPath>/naja</contextPath>
</webAppConfig>
<connectors>
<connector
implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>8080</port>
<host>localhost</host>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
<systemProperties>
<systemProperty>
<name>org.eclipse.jetty.util.URI.charset</name>
<value>utf-8</value>
</systemProperty>
</systemProperties>
</configuration>
</plugin>
</plugins>
好了,到这里测试工程就已经创建好了。
运行测试工程
右键点击工程名,run as或者debug as菜单,选择maven builer,在弹出的对话框的Goals中输入
jetty:run
然后点击run或者debug按钮,开始运行项目,会看到jetty启动的提示信息。
如果想修改配置,那么就点那个run configrations或者debug configrations,在弹出的对话框中修改即可。
好了,到这里测试工程已经建立完成,下一篇我们列出详细的加载过程。
SPING 3.2.X 源代码分析之六:CONTEXTLOADERLISTENER加载SPRING CONTEXT的过程
上文中,我们建立spring的测试工程,本文就正式开始通过调试的方式,阅读spring的源代码,了解其context环境的加载过程。
下载源码包
如果你没有导入spring 的源代码,那么可以在工程中的Maven Dependencies中,选定对应的jar包,在右键的maven菜单中点那个download sources菜单,系统会自动下载源码包到本地,并且和jar对应起来。
开始调试
找到ContextLoaderListener类。
1、ContextLoaderListener
该类继承ContextLoader类,实现了ServletContextListener接口,负责环境的加载启动。
listenet的入口当然是
public void contextInitialized(ServletContextEvent event)
方法。
所以,在此方法的第一行代码打上断点,然后用debug as的maven builder菜单就可以启动调试。
该方法由servlet的web容器调用,传入的ServletContextEvent可以取到servlet context,spring会在该context中建立自己的context环境。
然后调用ContextLoader类的
public WebApplicationContext initWebApplicationContext(ServletContext
servletContext)
方法。
2、ContextLoader
->public WebApplicationContext initWebApplicationContext(ServletContext
servletContext)
进入该方法后,创建context的主要代码如下所示
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
这段代码开始创建自己的context环境。
->protected
WebApplicationContext createWebApplicationContext(ServletContext sc)
Class<?> contextClass = determineContextClass(sc);
要确定当前环境下使用的具体的context类的类型。
->protected
Class<?> determineContextClass(ServletContext servletContext)
该方法先用“contextClass”参数,从SC的InitParameter中查找是否已经设置了context类型,如果存在就返回该类的Class对象。
否则就执行下面的代码
contextClassName =
defaultStrategies.getProperty(WebApplicationContext.class.getName());
而defaultStrategies属性是在static的块中初始化的
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());
}
}
其中
DEFAULT_STRATEGIES_PATH = "ContextLoader.properties"
而"ContextLoader.properties"文件和ContextLoader类在同一包中,内容如下:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
从这里可以看出,默认的情况下,web的应用都是使用的XmlWebApplicationContext类。
返回
->protected WebApplicationContext createWebApplicationContext(ServletContext
sc)
后续的代码会判断一下得到的类是不是ConfigurableWebApplicationContext的子类,这里肯定是了,可以了解一下ApplicationContext类的继承体系。
最后return
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
重新回到
->public WebApplicationContext initWebApplicationContext(ServletContext
servletContext)
方法,这时候context对象已经有了,后续就是组装context对象的内容了。
判断当前的context对象是否是激活的if
(!cwac.isActive()),如果没有激活就会执行下面的代码
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);
}
这里的两个判断都是调用的AbstractApplicationContext类的实现。
由于没有激活,同时,其parent对象也没有设置,在spring里面他们的parent就是ApplicationContext类。
会继续执行
ApplicationContext parent = loadParentContext(servletContext);
的代码,加载ApplicationContext对象。
->protected
ApplicationContext loadParentContext(ServletContext servletContext)
该方法将会返回null或者已经存在的ApplicationContext对象。
parentContext = (ApplicationContext) this.parentContextRef.getFactory();
这行代码,getFactory()返回的是BeanFactory接口,而ApplicationContext通过HierarchicalBeanFactory继承了该接口。
返回
->public WebApplicationContext initWebApplicationContext(ServletContext
servletContext)
处理完parent后,会执行
configureAndRefreshWebApplicationContext(cwac, servletContext);
这行代码。
->protected
void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext
wac, ServletContext sc)
wac.getId()
这个id是在AbstractApplicationContext类中设置,其原始值
private String id = ObjectUtils.identityToString(this);
然后就是判断SC中是否已经设置"contextId"参数。
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
如果已经存在,就将当前context的id设置为取到的
wac.setId(idParam);
否则,执行下面的代码
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// Servlet <= 2.4: resort to name specified in web.xml,
if
// any.
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX
+
ObjectUtils.getDisplayString(sc
.getServletContextName()));
} else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX
+
ObjectUtils.getDisplayString(sc.getContextPath()));
}
这里根据servlet的版本号来确定id的后缀。
其前缀是在ConfigurableWebApplicationContext接口中定义的
String APPLICATION_CONTEXT_ID_PREFIX = WebApplicationContext.class.getName() +
":";
运行的时候组装的实际结果大概如下
org.springframework.web.context.WebApplicationContext:/springweb
下面的代码精彩了
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
这里CONFIG_LOCATION_PARAM就是
public static final String CONFIG_LOCATION_PARAM =
"contextConfigLocation";
我们经常在web.xml中设置的parameter参数。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/context/spring/SpringContext.xml</param-value>
</context-param>
如果我们自定义了配置文件的位置,那么就会执行
wac.setConfigLocation(initParameter);
设置好自定义的文件位置。
否则,就会使用
"/WEB-INF/applicationContext.xml"的默认位置。
XmlWebApplicationContext类中DEFAULT_CONFIG_LOCATION和getDefaultConfigLocations()方法可以找到其设置。
设置完配置文件的位置,就要执行代码
customizeContext(sc, wac);
->protected
void customizeContext(ServletContext servletContext,
ConfigurableWebApplicationContext applicationContext)
执行
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
initializerClasses =
determineContextInitializerClasses(servletContext);
->protected
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
determineContextInitializerClasses(ServletContext servletContext)
这里面的泛型看着让人眼晕,其实就是断定一下当前的servlet context中已经初始化过参数的spring的context类。
如果已经存在,会把所有的context的类转换成对象后返回。
我们如果第一次启动调试进来,应该还是返回null。
返回
->protected void customizeContext(ServletContext servletContext,
ConfigurableWebApplicationContext applicationContext)
然后会直接return;
否则的话,会把initializerClasses逐个加入当前的applicationContext对象后返回。
返回
->protected void
configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac,
ServletContext sc)
这次返回后要执行一个很重要的方法
wac.refresh();
来刷新context。
3、AbstractApplicationContext
->public void refresh() throws BeansException, IllegalStateException
前面已经使用过多次该类的属性和方法,但相对来说不是很重要,而refresh()方法是这个类的重要方法。
->protected
void prepareRefresh()
synchronized (this.activeMonitor) {
this.active = true;
}
这里将active设置为了true,表示激活了。
4、AbstractRefreshableWebApplicationContext
->protected void initPropertySources()
这行代码
super.initPropertySources();
其实什么都没做,跑到AbstractApplicationContext中执行个空方法回来了。
ConfigurableEnvironment env = this.getEnvironment();
创建了一个StandardServletEnvironment对象,该对象通过其父类AbstractEnvironment实现了ConfigurableEnvironment。
((ConfigurableWebEnvironment)env).initPropertySources(
this.servletContext, this.servletConfig);
将servletContext设置了进去。
5、StandardServletEnvironment
->public void initPropertySources(ServletContext servletContext,
ServletConfig servletConfig)
WebApplicationContextUtils.initServletPropertySources(
this.getPropertySources(), servletContext, servletConfig);
这里从AbstractEnvironment类中取得MutablePropertySources对象,其实就是
[servletConfigInitParams,servletContextInitParams,jndiProperties,systemProperties,systemEnvironment]
web容器的一些配置项,具有前后顺序性。
6、WebApplicationContextUtils
->public static void initServletPropertySources(
MutablePropertySources
propertySources, ServletContext servletContext, ServletConfig servletConfig)
做了个替换就返回了,后面的一直返回到这里。
3、AbstractApplicationContext
->protected void prepareRefresh()
getEnvironment().validateRequiredProperties();
这行代码帮刚才建立的Environment给验证了一下。
7、AbstractPropertyResolver
中间跳了几层,最后到这里
->public void validateRequiredProperties()
进行实际的验证工作。
一直返回到这里。
3、AbstractApplicationContext
->public void refresh() throws BeansException, IllegalStateException
又一个核心人物登场了:
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
beanFactory可是IOC的核心呀。
->protected ConfigurableListableBeanFactory obtainFreshBeanFactory()
8、AbstractRefreshableApplicationContext
->protected final void refreshBeanFactory() throws BeansException
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
如果已经有了beanFactory则销毁关闭。
没有的话,就创建一个
DefaultListableBeanFactory beanFactory = createBeanFactory();
->protected
DefaultListableBeanFactory createBeanFactory()
需要执行这个方法
getInternalParentBeanFactory()
3、AbstractApplicationContext
->protected BeanFactory getInternalParentBeanFactory()
最终得到一个DefaultListableBeanFactory对象后返回。
8、AbstractRefreshableApplicationContext
->protected final void refreshBeanFactory() throws BeansException
设置ID后,到下面的方法。
->protected
void customizeBeanFactory(DefaultListableBeanFactory beanFactory)
执行完毕后返回refreshBeanFactory()方法继续执行。
9、XmlWebApplicationContext
->protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException
new XmlBeanDefinitionReader(beanFactory)
是在AbstractBeanDefinitionReader类中,初始化了resourceLoader和environment属性。
还有XmlBeanDefinitionReader中的一些默认属性值。
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
设置一些XmlBeanDefinitionReader对象的属性,然后就要开始读取加载了。
->protected
void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException
String[] configLocations = getConfigLocations();
这里就是取我们设置的或者默认的applicationContext.xml的文件位置。
如果存在就
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
遍历读取。
10、AbstractBeanDefinitionReader
->public int loadBeanDefinitions(String location, Set<Resource>
actualResources) throws BeanDefinitionStoreException
先取得刚才设置的resourceLoader对象,就是那个this->9、XmlWebApplicationContext。
这里会走这个分支
if (resourceLoader instanceof ResourcePatternResolver)
可以看一下XmlWebApplicationContext的继承体系图,太复杂了。
执行完毕了就返回。
8、AbstractRefreshableApplicationContext
->protected final void refreshBeanFactory() throws BeansException
this.beanFactory = beanFactory;
到这里,beanFactory已经创建好了。
返回。
3、AbstractApplicationContext
->public void refresh() throws BeansException, IllegalStateException
prepareBeanFactory(beanFactory);
->protected
void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory)
进来设置了一堆的属性。
LOAD_TIME_WEAVER_BEAN_NAME是一种面向切面的类织入方式。
运行完毕该方法返回,执行下一个方法。
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();
}
这一段代码很重要。
postProcessBeanFactory(beanFactory);
4、AbstractRefreshableWebApplicationContext
->protected void postProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory)
设置了一些新的属性。
3、AbstractApplicationContext
invokeBeanFactoryPostProcessors(beanFactory);
->protected void
invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory)
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);
会取得在context文件中设置的一些processor,比如
[org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0,
org.springframework.context.annotation.internalConfigurationAnnotationProcessor]
registerBeanPostProcessors(beanFactory);
->protected void registerBeanPostProcessors(ConfigurableListableBeanFactory
beanFactory)
initMessageSource();
->protected void initMessageSource()
这个也很常用,国际化的东西。
initApplicationEventMulticaster();
->protected void initApplicationEventMulticaster()
onRefresh();
->protected void onRefresh()
registerListeners()
->protected void registerListeners()
finishBeanFactoryInitialization(beanFactory);
->protected void
finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)
finishRefresh();
返回返回。
2、ContextLoader
->public WebApplicationContext initWebApplicationContext(ServletContext
servletContext)
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
this.context);
终于把context放入servlet context了。
返回。
1、ContextLoaderListener
->public void contextInitialized(ServletContextEvent event)
加载结束。
好了,到这里,我们对web应用的context的基本加载过程有了一个初步的认识,后续我们会进行分析。
下一篇我们讲spring mvc servlet的加载过程。
SPING 3.2.X 源代码分析之七:DISPATCHERSERVLET加载SPRING CONTEXT的过程
我们在上一篇sping 3.2.x 源代码分析之六:ContextLoaderListener加载spring context的过程中大概讲解了一个空的spring工程加载的过程,本篇讲一下spring mvc的context环境加载的过程。
修改测试工程
在web.xml文件中增加如下配置
<!– Spring
MVC Config –>
<servlet>
<servlet-name>mvcDispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvcDispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
这里的加载类org.springframework.web.servlet.DispatcherServlet,是一个servlet类,除了符合规则的url被交给该servlet处理外,这样子也可以确保spring mvc的加载过程是在spring的listener之后加载。该类可以配置多个,有些application就是通过配置多个,来分别处理系统前后台的请求。
在/WEB-INF/文件加下新建mvcDispatcher-servlet.xml文件,就是spring mvc的配置文件,这里文件名必须是配置的servlet name的名字+"-servlet.xml",等下我们阅读具体的代码执行的时候,会提到具体在哪里规定的这个文件的名字。
mvcDispatcher-servlet.xml文件的内容大概如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:property-placeholder location="classpath:config/default.properties" />
<context:component-scan base-package="org.javastar.framework.example.controller" />
<mvc:annotation-driven />
<mvc:default-servlet-handler />
<!–JSP config –>
<bean id="viewResolverCommon"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix"
value="/WEB-INF/page/" />
<property name="suffix"
value=".jsp" />
<property
name="viewClass">
<value>org.springframework.web.servlet.view.InternalResourceView
</value>
</property>
<property name="order"
value="2" />
</bean>
<!– This bean sets up the Velocity
environment for us based on a root path
for templates. Optionally, a properties
file can be specified for more control
over the Velocity environment, but the
defaults are pretty sane for file
based template loading. –>
<bean id="velocityConfig"
class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
<property name="resourceLoaderPath"
value="/WEB-INF/velocity/" />
<property
name="velocityProperties">
<props>
<prop key="input.encoding">UTF-8</prop>
<prop key="output.encoding">UTF-8</prop>
</props>
</property>
</bean>
<!– View resolvers can also be configured
with ResourceBundles or XML files.
If you need different view resolving
based on Locale, you have to use the
resource bundle resolver. –>
<bean id="viewResolverVM"
class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
<property
name="exposeSpringMacroHelpers" value="true" />
<property
name="requestContextAttribute" value="rc" />
<property
name="cache" value="true" />
<property name="prefix"
value="" />
<property name="suffix"
value=".vm" />
<property name="order"
value="1" />
<property
name="contentType" value="UTF-8" />
</bean>
<!– freemarker config –>
<bean id="freemarkerConfig"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property
name="templateLoaderPath" value="/WEB-INF/freemarker/"
/>
</bean>
<!– View resolvers can also be configured
with ResourceBundles or XML files.
If you need different view resolving
based on Locale, you have to use the
resource bundle resolver. –>
<bean id="viewResolverFM"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="cache"
value="true" />
<property name="prefix"
value="" />
<property name="suffix"
value=".ftl" />
<property name="order"
value="0" />
</bean>
</beans>
调试DispatcherServlet加载过程
1、DispatcherServlet类:
DispatcherServlet类继承FrameworkServlet类
其有两个重要的方法:
protected void doService(HttpServletRequest request, HttpServletResponse
response) throws Exception
->protected void doDispatch(HttpServletRequest request, HttpServletResponse
response) throws Exception
2、FrameworkServlet类:
FrameworkServlet继承HttpServletBean
其重要的方法包括下面两个方法:
public void refresh()
protected void service(HttpServletRequest request, HttpServletResponse
response)
3、HttpServletBean类:
该类继承自javax.servlet.http.HttpServlet
HttpServlet类我们比较熟悉了,其入口方法是
public void init() throws ServletException
就是servlet初始化的入口点。
了解一下servlet的生命周期就知道了。
HttpServletBean类的init()方法
public final void init() throws ServletException
该方法会准备
BeanWrapper bw =
PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader
= new ServletContextResourceLoader(getServletContext());
然后调用调用
FrameworkServlet.initServletBean()
方法,如果想要实现自己定制的加载机制,那就可以从替换FrameworkServlet类着手。
继续到FrameworkServlet的
->initServletBean()
->initWebApplicationContext()从这里开始,进入加载spring的context的过程。【重点关键入口】
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
由于前面已经加载过spring context,所以,这里也会取到已经加载进来的XmlWebApplicationContext的实例。
后续会
if (wac == null) {
// No context instance
is defined for this servlet -> create a local one
wac =
createWebApplicationContext(rootContext);
}
创建mvc的context,其最终的创建过程在
protected WebApplicationContext createWebApplicationContext(ApplicationContext
parent)
方法中完成。
然后进入
->protected void
configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac)【重点方法】
这个方法完成了环境的初始化和刷新。
在这个方法中
protected WebApplicationContext initWebApplicationContext()
会判断是否已经存在WebApplicationContext,如果存在则使用已经存在的对象进行配置的加载,如果不存在则新建一个环境。
主要是和是否已经使用ContextLoaderListener对象或者其他方式加载过spring的环境有关。
进入WebApplicationContextUtils类
->getWebApplicationContext(ServletContext sc, String attrName)
可能为null或者已经存在WebApplicationContext。
…..完成后,又执行
->initFrameworkServlet()。
直到加载完毕。
SPING 3.2.X 源代码分析之八:SPRING的各类BEANDEFINITIONPARSER解析器加载和使用
前面两篇文章,我们大致讲了spring和spring mvc的context的基本加载过程。这一篇开始讲具体的一些功能的情况及其在spring中的作用。
一、spring context的总体概况
1、org.springframework.context.ApplicationContext
衔接core中的Environment(EnvironmentCapable)
beans中的BeanFactory(ListableBeanFactory, HierarchicalBeanFactory)
同时,融入了MessageSource消息,ApplicationEventPublisher事件,ResourcePatternResolver资源查找。
2、从ApplicationContext衍生出application(ConfigurableApplicationContext)和web
application(WebApplicationContext)两大类context
对于web项目,我们最主要研究的是XmlWebApplicationContext。
3、Resource的方式读取资源
这样子不管是jar包中的还是文件夹中的普通文件,都以inputstream的方式获取,统一了获取的方法。
默认实现类DefaultResourceLoader中的
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be
null");
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new
ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()),
getClassLoader());
}
else {
try {
// Try to parse the
location as a URL…
URL url = new
URL(location);
return new
UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL ->
resolve as resource path.
return
getResourceByPath(location);
}
}
}
来通过三种不同的方式获取资源,所以,基本上不管是classpath中的,jar包中的,还是绝对路径下的资源都可以被找到。
4、资源的具体解析
XmlBeanDefinitionReader
[
XmlValidationModeDetector
public int detectValidationMode(InputStream inputStream) throws IOException
方法负责xml文档的具体解析
private String consumeCommentTokens(String line)
判断是否是有效的配置行
]
DefaultBeanDefinitionDocumentReader的
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate
delegate)
方法,负责具体的xml解析转换。
BeanDefinitionParserDelegate类定义了元素的解析方法。
public BeanDefinition parseCustomElement(Element ele, BeanDefinition
containingBd)
下一步
NamespaceHandlerSupport
private BeanDefinitionParser findParserForElement(Element element,
ParserContext parserContext)
下一步
AbstractBeanDefinitionParser
public final BeanDefinition parse(Element element, ParserContext parserContext)
下一步
AbstractSingleBeanDefinitionParser
protected final AbstractBeanDefinition parseInternal(Element element,
ParserContext parserContext)
下一步
PropertyPlaceholderBeanDefinitionParser
protected Class<?> getBeanClass(Element element)
这就到了具体的BeanDefinitionParser类,其他的还有很多类似的parser类。
二、parseHandler加载过程
这些parseHandler主要用做spring的xml配置文件中的不同命名空间下的元素的解析,比如<context> 、<bean>等等。
以spring context中自定义标签的parseHandler加载过程为例来简要说明一下。
spring自带标签bean的解析和普通用户自定义bean的解析过程不一样,所以,造成自带标签的解析不能使用${}形式的变量。
Spring的xml配置文件中有很多的spring自带的标签,这些标签的解析器的调用基本上都在NamespaceHandlerSupport中进行,而初始化都在NamespaceHandlerSupport的子类中进行。
parserContext是所有的parser类存放的上下文环境。
spring mvc的context加载过程中,是在BeanDefinitionParserDelegate中public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd)装了ParserContext。
而ParserContext的组装主要依赖于XmlReaderContext,其来自于spring包的"META-INF/spring.handlers"。
在XmlBeanDefinitionReader类中
public int registerBeanDefinitions(Document doc, Resource resource) throws
BeanDefinitionStoreException
的
protected XmlReaderContext createReaderContext(Resource resource)
创建。
******重点******
XmlBeanDefinitionReader类中指定了DefaultNamespaceHandlerResolver[该类中DEFAULT_HANDLER_MAPPINGS_LOCATION
= "META-INF/spring.handlers";指定,无法从解析spring.handlers的地方下手替换成自己的类]类,固定的类。
createDefaultNamespaceHandlerResolver()
其又从DefaultNamespaceHandlerResolver进化而来,
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION =
"META-INF/spring.handlers";
定义了所有的handlers的位置。
/**
* Load the specified NamespaceHandler mappings lazily.
*/
private Map<String, Object> getHandlerMappings() {
if (this.handlerMappings == null) {
synchronized (this) {
if
(this.handlerMappings == null) {
try
{
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation,
this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded
NamespaceHandler mappings: " + mappings);
}
Map<String, Object> handlerMappings = new
ConcurrentHashMap<String, Object>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings,
handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load
NamespaceHandler mappings from location [" + this.handlerMappingsLocation
+ "]", ex);
}
}
}
}
return this.handlerMappings;
}
注意,这里会扫描所有当前类路径下的classpath和jar包。
其默认加载
{http://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler,
http://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler,
http://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler,
http://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler,
http://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler,
http://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler,
http://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler,
http://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler,
http://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler,
http://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler}
spring-context包中包含:
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
spring-bean包中包含
http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
spring-aop包中包含
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
解析后生成XmlReaderContext
NamespaceHandlerResolver using mappings
{http://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler,
http://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler,
http://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler,
http://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler,
http://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler,
http://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler,
http://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler,
http://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler,
http://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler,
http://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler}
这里看context标签的解析过程
org.springframework.context.config.ContextNamespaceHandler
这个类负责xml中的context标签解析器的初始化。
其类的关系值得思考。
spring对其定义的XML配置标签context的解析过程分析
property-placeholder
property-override
annotation-config
component-scan
load-time-weaver
spring-configured
mbean-export
mbean-server
这些都会被放入NamespaceHandlerSupport类的
Map<String, BeanDefinitionParser> parsers属性中。
如果你想定义自己的扩展标签,那就从这里下手吧。
三、问题:为什么我们在<context:component-scan>标签中使用${}不会被解析?
BeanDefinitionParserDelegate类中
public BeanDefinition parseCustomElement(Element ele, BeanDefinition
containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler =
this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring
NamespaceHandler for XML schema namespace [" + namespaceUri +
"]", ele);
return null;
}
return handler.parse(ele, new
ParserContext(this.readerContext, this, containingBd));
}
在ComponentScanBeanDefinitionParser类中
public BeanDefinition parse(Element element, ParserContext
parserContext) {
String[] basePackages =
StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean
definitions and register them.
ClassPathBeanDefinitionScanner scanner =
configureScanner(parserContext, element);
Set<BeanDefinitionHolder>
beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions,
element);
return null;
}
看代码element.getAttribute(BASE_PACKAGE_ATTRIBUTE),这里直接取未替换过的参数。
DefaultBeanDefinitionDocumentReader类负责xml文档中元素的循环解析:
/**
* Parse the elements at the root level in the
document:
* "import", "alias",
"bean".
* @param root the DOM root element of the document
*/
protected void parseBeanDefinitions(Element root,
BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl =
root.getChildNodes();
for (int i = 0; i <
nl.getLength(); i++) {
Node node = nl.item(i);
if
(node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
AbstractBeanDefinitionParser类中
/**
* Register the supplied {@link BeanDefinitionHolder
bean} with the supplied
* {@link BeanDefinitionRegistry registry}.
* <p>Subclasses can override this method to
control whether or not the supplied
* {@link BeanDefinitionHolder bean} is actually even
registered, or to
* register even more beans.
* <p>The default implementation registers the
supplied {@link BeanDefinitionHolder bean}
* with the supplied {@link BeanDefinitionRegistry
registry} only if the {@code isNested}
* parameter is {@code false}, because one typically
does not want inner beans
* to be registered as top level beans.
* @param definition the bean definition to be
registered
* @param registry the registry that the bean is to be
registered with
* @see BeanDefinitionReaderUtils#registerBeanDefinition(BeanDefinitionHolder,
BeanDefinitionRegistry)
*/
protected void registerBeanDefinition(BeanDefinitionHolder
definition, BeanDefinitionRegistry registry) {
BeanDefinitionReaderUtils.registerBeanDefinition(definition, registry);
}
只有走bean标签的才会被PropertyPlaceholderBeanDefinitionParser进行变量替换。
四、从PropertyPlaceholderBeanDefinitionParser类入手分析spring的变量代换
PropertySourcesPlaceholderConfigurer和PropertyPlaceholderConfigurer的区别?可以自行去研究一下。
简单的说PropertySourcesPlaceholderConfigurer是PropertyPlaceholderConfigurer的升级推荐优先使用版,前者在Environment的使用上更灵活,同时支持了PropertySource。
PlaceholderConfigurerSupport这个类定义了变量的前后缀和默认值。
#BeanDefinitionParserDelegate
/**
* Parse property sub-elements of the given bean element.
*/
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) &&
nodeNameEquals(node, PROPERTY_ELEMENT)) {
parsePropertyElement((Element) node, bd);
}
}
}
用来替换变量。
#public void parsePropertyElement(Element ele, BeanDefinition bd)
#public Object parsePropertyValue(Element ele, BeanDefinition bd, String
propertyName)
#public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor
attributeAccessor)
#DefaultBeanDefinitionDocumentReader
#protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate
delegate)
AbstractApplicationContext
public void refresh() throws BeansException, IllegalStateException (这个方法很重要)
->protected void
invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory)
->for (String ppName : postProcessorNames) {
->invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors,
beanFactory);
PropertySourcesPlaceholderConfigurer
->public void postProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory) throws BeansException
->protected void doProcessProperties(ConfigurableListableBeanFactory
beanFactoryToProcess,
StringValueResolver
valueResolver)
BeanDefinitionVisitor
->public void visitBeanDefinition(BeanDefinition beanDefinition)
->protected void visitPropertyValues(MutablePropertyValues pvs)
->protected Object resolveValue(Object value)
->protected String resolveStringValue(String strVal)
protected String resolveStringValue(String strVal) {
if (this.valueResolver == null) {
throw new IllegalStateException("No
StringValueResolver specified – pass a resolver " +
"object into the constructor or override the ‘resolveStringValue’
method");
}
String resolvedValue =
this.valueResolver.resolveStringValue(strVal);
// Return original String if not modified.
return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
}
PropertySourcesPlaceholderConfigurer
->public String resolveStringValue(String strVal)
protected void processProperties(ConfigurableListableBeanFactory
beanFactoryToProcess,
final ConfigurablePropertyResolver
propertyResolver) throws BeansException {
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
propertyResolver.setValueSeparator(this.valueSeparator);
StringValueResolver valueResolver = new
StringValueResolver() {
public String resolveStringValue(String
strVal) {
String resolved =
ignoreUnresolvablePlaceholders ?
propertyResolver.resolvePlaceholders(strVal) :
propertyResolver.resolveRequiredPlaceholders(strVal);
return
(resolved.equals(nullValue) ? null : resolved);
}
};
doProcessProperties(beanFactoryToProcess,
valueResolver);
}
上面的代码展示了进行配置文件中的${}变量解析成对应值的基本过程。

浙公网安备 33010602011771号