第一部分 核心实现
第1章 Spring整体架构和环境搭建
Spring 是于 2003 年兴起的一个轻量级 Java 开源框架 ,由 Rod Johnson 在其著作 Expert One-On-One J2EE Design and Development 中阐述的部分理念和原型衍生而来 。 Spring 是为了解决企业应用开发的复杂性而创建的 ,它使用基本的 JavaBean 来完成以前只可能由 EJB 完成的 事情。
1.1 Spring 的整体架构
Spring框架是一个分层架构,它包含一系列的功能要素,并被分为大约 20个模块,如图 1-1 所示。
1. Core Containe
Core Container (核心容器)包含有 Core、 Beans、 Context和 ExpressionLanguage模块。
Core 和 Beans 模块是框架的基础部分,提供 IoC (转控制)和依赖注入特性。基础概念是 BeanFactory,它提供对Factory 模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置 。
- Core 模决主要包含 Spring 框架基本的核心工具类,Spring 的其他纽件都要用到这个包里的类, Core 模块是其他纽件的基本核心 。
- Beans 模块是所有应用都妥用到的,它包含访问配置文件、创建和管理 bean 以及进行 Inversion of Control I Dependency Injection ( IoC/DI )操作相关的所有类 。
- Context 模块构建于 Core 和 Beans模块基础之上,提供了一种类似于JNDI 注册器的框架式的对象访问方法 。Context 模块继承了 Beans 的特性,为 Spring 核心提供了大量扩展,添加了对国际化(例如资源绑定)、事件传播、资源加载和对 Context 的 透明创建的支持。ApplicationContext 接口是 Context 模块的关键 。
- Expression Language 模块提供了强大的表达式语言,用于在运行时查询和操纵对象。
2. Data Access/Integration
DataAccess/Integration层包含JDBC、 ORM、 OXM、JMS和 Transaction模块。
- JDBC 模块提供了一个 JDBC 抽象层,它可以消除冗长的 JDBC 编码和解析数据库厂商特有的错误代码。这个模块包含了 Spring 对 JDBC 数据访问进行封装的所有类
- ORM 模块为流行的对象-关系映射 API,如 JPA、 JDO、 Hibernate、 iBatis 等,提供了 一个交互层。
-
OXM 模块提供了一个对 Objec/XML 映射实现的抽象层, Object/XML 映射实现包括JAXB、 Castor、 XMLBeans、 JiBX 和 XStrearn。
-
JMS ( Java Messaging Service )模块主要包含了 一些制造和消费消息的特性。
- Transaction 模块支持编程和声明性的事务管理,这些事务类必须实现特定的接口,并且对所有的 POJO 都适用 。
3. Web
Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。Web模块还简化了处理大部分请求以及将请求参数绑定到域对象的工作。Web层包含了 Web、 Web-Servlet、 Web-Struts和Web-Porlet模块。
4. AOP
AOP模块提供了一个符合 AOP联盟标准的面向切面编程的实现,它让你可以定义例如方法拦截器和切点,从而将逻辑代码分开,降低它们之间的调合性 。 利用 source-level 的元数据 功能,还可以将各种行为信息合并到你的代码中,这有点像.Net技术中的 attribute概念。
通过配置管理特性, SpringAOP 模块直接将面向切面的编程功能集成到了 Spring 框架中, 所以可以很容易地使 Spring 框架管理的任何对象支持 AOP。 Spring AOP 模块为基于 Spring 的 应用程序中的对象提供了事务管理服务 。 通过使用 SpringAOP,不用依赖 EJB 组件,就可以将 声明性事务管理集成到应用程序中 。
5. Test
Test模块支持使用 JUnit和 TestNG 对 Spring组件进行测试 。
1.2 环境搭建
第 2 章 容器的基本实现
2.1 容器基本用法
bean 是 Spring 中最核心的东西,Spring 就像是个大水桶,而 bean 就像是容器中的水,水桶脱离了水便也没什么用处了,那么我们先看看 bean 的定义 。
这么看来bean并没有任何特别之处,的确,Spring的目的就是让我们的bean能成为一个纯粹的POJO,这也是Spring所追求的。接下来看看配置文件:
在上面的配置中bean的声明方式,尽管Spring中bean的元素定义着N种属性来支撑业务的各种应用。编写测试代码测试。
直接使用BeanFactory作为容器对于Spring的使用来说并不多见,在企业级的应用中大多数都会使用的是ApplicationContext。
2.2 功能分析
上面测试代码完成的功能无非就是以下几点:读取配置文件beanFactoryTest.xml,根据配置文件找到对应的类的配置,并实例化。调用实例化后的实例。
2.3 工程搭建
2.4 Spring的结构组成
2.4.1 beans包的层级结构
整个beans工程的源码结构,如图2-3所示。beans包中的各个源码包的功能如下:
- src/main/java用于展现Spring的主要逻辑。
- src/main/resources用于存放系统的配置文件。
- src/test/java用于对主要逻辑进行单元测试。
- src/test/resources用于存放测试用的配置文件。
图2-3 beans工程的源码结构
2.4.2 核心类介绍
在正式开始源码分析之前,有必要了解Spring中核心的两个类。
1.DefaultListableBeanFactory
XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现。对于XmlBeanFactory与DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。图2-4是ConfigurableListableBeanFactory的层次结构图,图2-5是相关类图。
图2-4 ConfigurableListableBeanFactory的层次结构图
图2-5 容器加载相关类图
先简单地了解图2-5中各个类的作用:
- AliasRegistry:定义对alias的简单增删改等操作。
- SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现。
- SingletonBeanRegistry:定义对单例的注册及获取。
- BeanFactory:定义获取bean及bean的各种属性。
- DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry各函数的实现。
- HierarchicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持。
- BeanDefinitionRegistry:定义对BeanDefinition的各种增删改操作。
- FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能。
- ConfigurableBeanFactory:提供配置Factory的各种方法。
- ListableBeanFactory:根据各种条件获取bean的配置清单。
- AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能。
- AutowireCapableBeanFactory:提供创建bean、自动注入、初始化以及应用bean的后处理器。
- AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapable BeanFactory进行实现。
- ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口等。
- DefaultListableBeanFactory:综合上面所有功能,主要是对bean注册后的处理。
XmlBeanFactory对DefaultListableBeanFactory类进行扩展,主要用于从XML文档中读取BeanDefinition,对于注册及获取bean都是使用从父类DefaultListableBeanFactory继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性。在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册。
2.XmlBeanDefinitionReader
XML配置文件的读取是Spring中重要的功能,Spring的大部分功能都是以配置作为切入点的,可以从XmlBeanDefinitionReader中梳理一下资源文件读取、解析及注册的大致脉络,首先看看各个类的功能:
- ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource。
- BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个功能。
- EnvironmentCapable:定义获取Environment方法。
- DocumentLoader:定义从资源文件加载到转换为Document的功能。
- AbstractBeanDefinitionReader:对EnvironmentCapable、BeanDefinitionReader类定义的功能进行实现。
- BeanDefinitionDocumentReader:定义读取Docuemnt并注册BeanDefinition功能。
- BeanDefinitionParserDelegate:定义解析Element的各种方法。
经过以上分析,可以梳理出整个XML配置文件读取的大致流程,如图2-6所示,在XmlBeanDifinitionReader中主要包含以下几步的处理:
图2-6 配置文件读取相关类图
- 通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourLoader将资源文件路径转换为对应的Resource文件。
- 通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Document文件。
- 通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析。
2.5 容器的基础XmlBeanFactory
接下来要深入分析以下功能的代码实现:
BeanFactory bf= new XmlBeanFactory (new ClassPathResource (”beanFactoryTest .xml”));
通过XmlBeanFactory初始化时序图(如图2-7所示)看一看上面代码的执行逻辑。
图 2-7 XmlBeanFactory初始化时序图
2.5.1 配置文件封装
Spring 的配置文件读取是通过 ClassPathResource 进行封装的,如 new ClassPathResource (”beanFactoryTest.xml"),那ClassPathResourc巳完成了什么功能呢?
在 Java 中,将不同来源的资源抽象象成 URL,通过注册不同的 handler( URLStreamHandler ) 来处理不同来源的资源的读取逻辑。 Spring 对其内部使用到的资源实现了自己的抽象结构 : Resource 接口封装底层资源 。
InputStreamSource 封装任何能返回 InputStream 的类,比如 File、Classpath下的资源和ByteArray等。它只有一个方法定义:getlnputStream(),该方法返回一个新的InputStream对象。
Resource接口抽象了所有 Spring内部使用到的底层资源: File、URL、Classpath等。 首先, 它定义了3个判断当前资源状态的方法:存在性( exists )、可读性( isReadable )、是否处于打 开状态(isOpen)。 另外,Resomce接口还提供了不同资源到URL、URI、File类型的转换,以及获取 lastModified 属性、文件名(不带路径信息的文件名, getFilename())的方法 。 为了便于操作, Resource 还提供了基于当前资源创建一个相对资源的方法: createRelative()。 Resource 还提供了 getDescription()方法用来在错误处理中打印信息 。
对不同来源的资源文件都有相应的 Resource 实现 : 文件( FileSystemResource )、 Classpath 资源( ClassPathResource )、 URL 资源( UrlResource )、 InputStream 资源( InputStreamResource )、 Byte 数组( ByteArrayResource )等 。
有了 Resource 接口便可以对所有资源文件进行统一处理。 至于实现,其实是非常简单的,
以 getlnputStream 为例,ClassPathResource 巾的实现方式便是通过 class 或者 classLoader 提供的底层方法进行调用,而对于 Fi leSystemResource 的实现其实更简单,直接使用FileinputStream对文件进行实例化。
ClassPathResource.java
FileSystemResource.java
当通过Resource相关类完成了对配置文件进行封装后配置文件的读取工作就全权交给XmlBeanDefinitionReader 来处理。
了解了Spring中将配置文件封装为Resource类型的实例方法后,继续探寻XmlBeanFactory的初始化过程了,在这里分析的是使用Resource实例作为构造函数参数的办法,代码如下:
XmlBeanFactory.java
构造函数内部再次调用内部构造函数:
上面函数中的代码this.reader.loadBeanDefinitions(resource)是资源加载的真正实现,也是分析的重点之一。XmlBeanDefinitionReader加载数据前还有一个调用父类构造函数初始化的过程:super(parentBeanFactory),跟踪代码到父类AbstractAutowireCapableBeanFactory的构造函数中:
AbstractAutowireCapableBeanFactory.java
ignoreDependencyInterface的主要功能是忽略给定接口的自动装配功能,那么,这样做的目的是什么呢?会产生什么样的效果呢?
举例来说,当A中有属性B,那么当Spring在获取A的Bean的时候如果其属性B还没有初始化,那么Spring会自动初始化B,这也是Spring中提供的一个重要特性。但是,某些情况下,B不会被初始化,其中的一种情况就是B实现了BeanNameAware接口。Spring中是这样介绍的:自动装配时忽略给定的依赖接口,典型应用是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者ApplicationContext通过ApplicationContextAware进行注入。
2.5.2 加载Bean
在XmlBeanFactory构造函数中调用了XmlBeanDefinitionReader类型的reader属性提供的方法this.reader.loadBeanDefinitions(resource),而这句代码则是整个资源加载的切入点,先来看看这个方法的时序图,如图2-9所示。
图2-9 loadBeanDefinitions函数执行时序图
从上面的时序图中尝试梳理整个的处理过程如下:
- 封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodedResource类进行封装。
- 获取输入流。从Resource中获取对应的InputStream并构造InputSource。
- 通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。
loadBeanDefinitions函数具体的实现过程:
EncodedResource的作用是什么呢?这个类主要是用于对资源文件的编码进行处理的。其中的主要逻辑体现在getReader()方法中,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码。
上面代码构造了一个有编码(encoding)的InputStreamReader。当构造好encodedResource对象后,再次转入了可复用方法loadBeanDefinitions(new EncodedResource(resource))。这个方法内部才是真正的数据准备阶段,也就是时序图所描述的逻辑:
再次整理数据准备阶段的逻辑,首先对传入的resource参数做封装,目的是考虑到Resource可能存在编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分doLoadBeanDefinitions(inputSource,encodedResource.getResource())。
在上面冗长的代码中假如不考虑异常类的代码,其实只做了三件事,这三件事的每一件都必不可少:
- 获取对XML文件的验证模式。
- 加载XML文件,并得到对应的Document。
- 根据返回的Document注册Bean信息。
这3个步骤支撑着整个Spring容器部分的实现,尤其是第3步对配置文件的解析,逻辑非常的复杂,我们先从获取XML文件的验证模式讲起。
2.6 获取XML的验证模式
XML文件的验证模式保证了XML文件的正确性,常用的验证模式有两种:DTD和XSD。
2.6.1 DTD与XSD区别
DTD(Document Type Definition)即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正确。 一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。
要使用DTD验证模式的时候需要在XML文件的头部声明,以下是在Spring中使用DTD声明方式的代码:
以Spring为例,具体的Spring-beans-2.0.dtd部分如下:
XML Schema语言就是XSD(XML Schemas Definition)。XML Schema描述了XML文档的结构。可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其要求。文档设计者可以通过XML Schema指定XML文档所允许的结构和内容,并可据此检查XML文档是否是有效的。XML Schema本身是XML文档,它符合XML语法结构。可以用通用的XML解析器解析它。
2.6.2 验证模式的读取
Spring通过getValidationModeForResource方法来获取对应资源的的验证模式。
自动检测验证模式的功能是在函数detectValidationMode方法中实现的,在detectValidationMode函数中又将自动检测验证模式的工作委托给了专门处理类XmlValidationModeDetector,调用了XmlValidationModeDetector的validationModeDetector方法,具体代码如下:
Spring用来检测验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD。
2.7 获取Document
经过了验证模式准备的步骤就可以进行Document加载了,XmlBeanFactoryReader类对于文档读取委托给DocumentLoader去执行,这里的DocumentLoader是个接口,而真正调用的是DefaultDocumentLoader,解析代码如下:
DefaultDocumentLoader.java
2.7.1 EntityResolver用法
在loadDocument方法中涉及一个参数EntityResolver,何为EntityResolver?官网这样解释: 如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例。也就是说,对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证。默认的寻找规则,即通过网络(实现上就是声明的DTD的URI地址)来下载相应的DTD声明,并进行认证。
EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可。
2.8 解析及注册BeanDefinitions
当程序已经拥有XML文档文件的Document实例对象时,就会被引入下面这个方法。