第一部分 核心实现
第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实例对象时,就会被引入下面这个方法。
XmlBeanDefinitionReader.java

BeanDefinitionDocumentReader是一个接口,而实例化的工作是在createBeanDefinitionDocumentReader()中完成的,真正的类型是DefaultBeanDefinitionDocumentReader,该方法的重要目的之一就是提取 root,以便于再次将root作为参数继续BeanDefinition的注册。

2.8.1 profile属性的使用
分析profile前我们先了解下profile的用法,官方示例代码片段如下:

集成到Web环境中时,在web.xml中加入以下代码:

有了这个特性就可以同时在配置文件中部署两套配置来适用于生产环境和开发环境,这样可以方便的进行切换开发、部署环境,最常用的就是更换不同的数据库。
程序会获取beans节点是否定义了profile属性,如果定义了则会需要到环境变量中去寻找,所以这里首先断言environment不可能为空,因为profile是可以同时指定多个的,需要程序对其拆分,并解析每个profile是都符合环境变量中所定义的,不定义则不会浪费性能去解析。
2.8.2 解析并注册BeanDefinition
处理profile后进行XML的读取,跟踪代码进入parseBeanDefinitions(root,this.delegate)。

上面的代码看起来逻辑还是蛮清晰的,因为在Spring的XML配置里面有两大类Bean声明,一个是默认的,如:
![]()
另一类就是自定义的,如:
![]()
两种方式的读取及解析差别是非常大的,对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement方法对自定义命名空间进行解析。而判断是否默认命名空间还是自定义命名空间的办法其实是使用node.getNamespaceURI()获取命名空间,并与Spring中固定的命名空间http://www.springframework.org/schema/beans进行比对。如果一致则认为是默认,否则就认为是自定义。
第3章 默认标签的解析
默认标签的解析是在parseDefaultElement函数中进行的,分别对4种不同标签(import、alias、bean和beans)做了不同的处理。

3.1 bean标签的解析及注册
大致的逻辑总结如下:
- 首先委托BeanDefinitionDelegate类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder类型的实例bdHolder,经过这个方法后,bdHolder实例已经包含我们配置文件中配置的各种属性了,例如class、name、id、alias之类的属性。
- 当返回的bdHolder不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。
- 解析完成后,需要对解析后的bdHolder进行注册,同样,注册操作委托给了Bean-DefinitionReaderUtils的registerBeanDefinition方法。
- 最后发出响应事件,通知相关的监听器,这个bean已经加载完成了。
3.1.1 解析BeanDefinition
首先从元素解析及信息提取开始,也就是BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele),进入BeanDefinitionDelegate类的parseBeanDefinitionElement方法。
BeanDefinitionDelegate.java
在当前层完成的主要工作包括如下内容:
- 提取元素中的id以及name属性。
- 进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中。
- 如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName。
- 将获取到的信息封装到BeanDefinitionHolder的实例中。
进一步地查看步骤2中对标签其他属性的解析过程:


接下来,我们继续一些复杂标签属性的解析。
1.创建用于属性承载的BeanDefinition
BeanDefinition是一个接口,在Spring中有三种实现:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition,实现均继承了AbstractBeanDefiniton,其中BeanDefinition是配置文件<bean>元素标签在容器中的内部表示形式。<bean>元素标签拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应的beanClass、scope、lazyInit属性,BeanDefinition和<bean>中的属性是一一对应的。其中RootBeanDefinition是最常用的实现类,它对应一般性的<bean>元素标签,GenericBeanDefinition是自2.5版本以后新加入的bean文件配置属性定义类,是一站式服务类。
在配置文件中可以定义父<bean>和子<bean>,父<bean>用RootBeanDefinition表示,而子<bean>用ChildBeanDefiniton表示,而没有父<bean>的<bean>就使用RootBeanDefinition表示。AbstractBeanDefinition对两者共同的类信息进行抽象。
Spring通过BeanDefinition将配置文件中的<bean>配置信息转换为容器的内部表示,并将这些BeanDefiniton注册到BeanDefinitonRegistry中。Spring容器的BeanDefinitionRegistry就像是Spring配置信息的内存数据库,主要是以map的形式保存,后续操作直接从BeanDefinitionRegistry中读取配置信息。它们之间的关系如图3-2所示。

图3-2 BeanDefinition及其实现类
由此可知,要解析属性首先要创建用于承载属性的实例,也就是创建GenericBeanDefinition类型的实例。而代码createBeanDefinition(className, parent)的作用就是实现此功能。

BeanDefinitionReaderUtils.java

2.解析各种属性
当创建了bean信息的承载实例后,便可以进行bean信息的各种属性解析了。parseBeanDefinitionAttributes方法是对element所有元素属性进行解析:



3.解析子元素meta 
当需要使用里面的信息的时候可以通过BeanDefinition的getAttribute(key)方法进行获取。 
4.解析子元素lookup-method
子元素lookup-method 称它为获取器注入。获取器注入是一种特殊的方法注入,它是把一个方法声明为返回某种类型的bean,但实际要返回的bean是在配置文件里面配置的,此方法可用在设计有些可插拔的功能上,解除程序依赖。
看看具体的应用。
1.首先我们创建一个父类。

2.创建其子类并覆盖showMe方法。

3.创建调用方法。

4.创建测试方法。
抽象方法还没有被实现,怎么可以直接调用呢?答案就在Spring为我们提供的获取器中,我们看看配置文件是怎么配置的。

在配置文件中的lookup-method子元素,这个配置完成的功能是动态地将teacher所代表的bean作为getBean的返回值,运行测试方法我们会看到控制台上的输出:
当业务变更或者在其他情况下,teacher里面的业务逻辑已经不再符合业务要求,需要进行替换怎么办呢?这时需要增加新的逻辑类:

同时修改配置文件:
了解了lookup-method子元素所提供的大致功能,这时再次去看它的属性提取源码会觉得更有针对性。

5.解析子元素replaced-method
在开始提取分析之前我们还是预先介绍下这个元素的用法。方法替换:可以在运行时用新的方法替换现有的方法。与之前的look-up不同的是,replaced-method不但可以动态地替换返回实体bean,而且还能动态地更改原有方法的逻辑。我们来看看使用示例。
1.在changeMe中完成某个业务逻辑。
2.在运营一段时间后需要改变原有的业务逻辑。

3.使替换后的类生效。

4. 测试

运行测试类就可以看到预期的结果了,控制台成功打印出“我替换了原有的方法”,也就是说我们做到了动态替换原有方法,知道了这个元素的用法,我们再次来看元素的提取过程:

可以看到无论是look-up还是replaced-method都是构造了一个MethodOverride,并最终记录在了AbstractBeanDefinition中的methodOverrides属性中。
6.解析子元素constructor-arg
对构造函数的解析是非常常用的,同时也是非常复杂的,也相信大家对构造函数的配置都不陌生,举个简单的小例子:

对于constructor-arg子元素的解析,Spring是通过parseConstructorArgElements函数来实现的,具体的代码如下:




上面一段看似复杂的代码让很多人失去了耐心,但是,涉及的逻辑其实并不复杂,首先是提取constructor-arg上必要的属性(index、type、name)。
如果配置中指定了index属性,那么操作步骤如下。
- 解析Constructor-arg的子元素。
- 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素。
- 将type、name和index属性一并封装在ConstructorArgumentValues.ValueHolder类型中并添加至当前BeanDefinition的constructorArgumentValues的indexedArgumentValues属性中。
如果没有指定index属性,那么操作步骤如下。
- 解析constructor-arg的子元素。
- 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素。
- 将type、name和index属性一并封装在ConstructorArgumentValues.ValueHolder类型中并添加至当前BeanDefinition的constructorArgumentValues的genericArgumentValues属性中。
那么了解了整个流程后,我们尝试着进一步了解解析构造函数配置中子元素的过程,进入parsePropertyValue:


第 4 章 自定义标签的解析
当完成从配置文件到 Document 的转换并提取对应的 root 后,便开始了默认标签与自定义标签的解析:

当 Spring 拿到一个元素时,首先根据命名空间进行解析,如果是默认的命名空间,则使用 parseDefaultElement 方法进行元素解析,否则使用 parseCustomElement 方法进行解析。
4.1 自定义标签使用
Spring 提供可扩展的Schema 的支持,扩展 Spring 自定义标签配置大致需要以下几个步骤:
- 创建一个需要扩展的组件。
- 定义一个 XSD 文件描述组件内容。
- 创建一个文件,实现 BeanDefinitionParser 接口,用来解析 XSD 文件中的定义和组件定义。
- 创建一个 Handler 文件,扩展自 NamespaceHandlerSupport ,目的是将组件注册到 Spring容器 。
- 编写 Spring.handlers 和 Spring.schemas 文件 。
按照上面的步骤体验自定义标签的过程 :
1. 首先创建一个普通的 POJO ,用来接收配置文件。

2. 定义一个 XSD 文件描述组件内容 。

在上面的 XSD 文件中描述了一个新的 targetNamespace,并在这个空间中定义了一个 name 为user 的 element, user 有 3 个属性 id、userName 和 email,其中 email 的类型为String 。这3 个类主要用于验证 Spring 配置文件中自定义格式。
3. 创建一个类实现BeanDefinitionParser 接口,用来解析 XSD 文件中的定义和组件定义。

4 . 创建一个 Handler 文件,扩展自 NamespaceHandlerSupport,目的是将组件注册到Spring 容器 。

以上代码无非是当遇到向定义标签<user:aaa 这样类似于以 user 开头的元素,就会把这个元素扔给对应的 UserBeanDefinitionParser 去解析 。
5. 编写 Spring.handlers 和 Spring.schemas 文件, 默认位置是在工程的META-INF文件夹下,当然可以通过 Spring 的扩展或者修改源码的方式改变路径 。

到这里自定义的配置就结束了,而Spring 加载自定义的大致流程是遇到自定义标签然后就去 Spring.handlers 和 Spring.schemas 中去找对应的 handler 和 XSD,默认位置是/META-INF/下,进而有找到对应的 handler 以及解析元素的Parser,从而完成了整个自定义元素的解析。
6 创建测试配置文件,在配置文件中引入对应的命名空间以及XSD后,便可以直接使用自定义标签了 。

7. 测试。
不出意外的话,应该看到了期待的结果,控制台上打印出:aaa , bbb
4.2 自定义标签解析
接下来探索自定义标签的解析过程。

上述代码无非是根据对应的bean获取对应的命名空间 ,根据命名空间解析对应的处理器,然后根据用户自定义的处理器进行解析。
4.2.1 获取标签的命名空间
标签的解析是从命名空间的提起开始的,无论是Spring默认标签还是自定义标签,都是以标签所提供的命名空间为基础的,提取对应元素的命名空间使用的是org.w3c.dom.Node 中提供的方法:

4.2.2 提取自定义标签处理器
有了命名空间就可以进行NamespaceHandler的提取


当得到自定义命名空间处理后会马上执行narnespaceHandler.init()来进行自定义 BeanDefinitionParser的注册。注册后,命名空间处理器就可以根据标签的不同来调用不同的解析器进行解析。
4.2.3 标签解析
得到了解析器以及要分析的元素后,Spring 就可以将解析工作委托给自定义解析器去解析了。在 Spring 中的代码为:
return handler.parse(ele , new ParserContext ( this.readerContext , this , containingBd))
自定义解析器没有这个parse方法,该方法是父类中的实现,查看父类NamespaceHandlerSupport中的 parse 方法 。
解析过程中首先是寻找元素对应的解析器,进而调用解析器中的 parse 方法。
第 5 章 bean 的加载
经过前面的分析,结束了对 XML 配置文件的解析,接下来对bean加载探索。对于加载bean 的功能,在 Spring中的调用方式为:
MyTestBean bean=(MyTestBean) bf.getBean( "myTestBean" )
这句代码实现了什么样的功能呢? 可以先快速体验一下 Spring 中代码是如何实现的。




对于加载过程中所涉及的步骤大致如下:
1 . 转换对应 bean Name
获取bean时传入的参数name可能是别名,也可能是FactoryBean ,所以需要进行一系列的解析,这些解析内容包括如下内容 。
- 去除FactoryBean 的修饰符,也就是如果name="&aa",那么会首先去除&而使 name="aa"。
- 取指定alias所表示的最终beanName,例如别名A指向名称为B的 bean 则返回 B;若别名A指向别名B,别名B又指向名称为C的 bean 则返回 C。
2. 尝试从缓存中加载单例
单例在Spring的同一个容器内只会被创建一次,后续再获取bean,就直接从单例缓存中获
取了。当然这里也只是尝试加载,首先尝试从缓存中加载,如果加载不成功则再次尝试从
singletonFactories 中加载。因为在创建单例bean的时候会存在依赖注入的情况,而在创建依赖
的时候为了避免循环依赖,在 Spring 中创建 bean 的原则是不等 bean 创建完成就会将创建 bean
的 ObjectFactory 提早曝光加入到缓存中 一旦下一个 bean 创建时候需要依赖上一个 bean 则直
接使用ObjectFactory。
3. bean 的实例化
如果从缓存中得到了 bean 的原始状态,则需要对 bean 进行实例化。缓存中记录的只是最原始的 bean 状态,并不一定是我们最终想要的 bean,getObjectForBeanlnstance完成bean的实例化。
4. 原型模式的依赖检查
只有在单例情况下才会尝试解决循环依赖,如果存在A中有B的属性,B中有A的属性,那么当依赖注入的时候就会产生当 A 还未创建完的时候因为对于 B 的创建再次返回创建 A, 造成循环依赖,也就是情况: isPrototypCurrentlyInCreation(beanName)判断 true。
5. 检测parentBeanFactory
从代码上看,如果缓存没有数据的话直接转到父类工厂上去加载,这是为什么呢?代码里有一个很重要的判断条件: parentBeanFactory!=null&&!containsBeanDefinition(beanName), parentBeanFactory != null是基础,!containsBeanDefinition(beanName)是在检测如果当前加载的 XML 配置文件中不包含 beanName所对应的配置,就只能到parentBeanFactory 去尝试下了,然后再去递归的调用getBean方法。
6. 将存储XML配置文件的GernericBeanDefinition转换为RootBeanDefinition
XML配置文件中读取到的bean信息是存储在GenericBeanDefinition中,但是所有的bean后续处理都是针对于 RootBeanDefinition的 ,所以需要进行转换,转换的同时如果父类 bean不为空的话,则会一并合并父类的属性。
7. 寻找依赖
bean 的初始化过程中很可能会用到某些属性,而某些属性很可能是动态配置的,并且配置成依赖于其他的bean,那么这个时候就有必要先加载依赖的 bean,所以在Spring 的加载顺序中,在初始化某一个 bean 的时候首先会初始化这个 bean所对应的依赖。
8. 针对不同的scope进行bean的创建
在Spring中存在着不同的scope,默认的是singleton,还有些其他配置诸如prototype、request之类的。在这个步骤中,Spring会根据不同的配置进行不同的初始化策略 。
9. 类型转换
程序到这里返回bea后已经基本结束了,通常对该方法的调用参数requiredType是为空的,但是可能会存在这样的情况,返回的bean其实是个String但是 requiredType却传入Integer 类型,那么这时候本步骤就会起作用了,它的功能是将返回的 bean转换为 requiredType所指定的类型。 当然,String转换为Integer是最简单的一种转换,在Spring中提供了各种各样的转换器,用户也可以自己扩展转换器来满足需求。
经过上面的步骤后bean的加载就结束了,这个时候就可以返回我们所需要的bean了。在细化分析各个步骤提供的功能前,先了解下FactoryBean的用法 。
5.1 FactoryBean 的使用
一般情况下,Spring 通过反射机制利用bean的class属性指定实现类来实例化bean。在某些情况下,实例化 bean 过程比较复杂,如果按照传统的方式,则需要在<bean>中提供大量的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案 。Spring提供了一个 org.Springframework.bean.factory.FactoryBean 的工厂类接口,可以通过实现该接口定制实例化 bean 的逻辑。

当配置文件中<bean>的 class 属性配置的实现类是 FactoryBean 时,通过 getBean()方法返回的不是 FactoryBean 本 身,而是 FactoryBean#getObject()方法所返回的对象,相当于 FactoryBean#getObject()代理了 getBean()方法。
如果希望获取FactoryBean的实例,则需要在使用getBean(beanName)方法时在beanName 前显示的加上"&"前缀,例如:getBean("&car")。
5.2 缓存中获取单例 bean
单例在Spring的同一个容器内只会被创建一次,后续再获取 bean 直接从单例缓存中获取。当然这里也只是尝试加载,首先尝试从缓存中加载,然后再次尝试尝试从 singletonFactories中加载。因为在创建单例 bean 的时候会存在依赖注入的情况,而在创建依赖的时候为了避免循环依赖,Spring创建bean的原则是不等 bean创建完成就会将创建bean的ObjectFactory提早曝光加入到缓存中,一旦下一个 bean 创建时需要依赖上个 bean,则直接使用 ObjectFactory。


这个方法因为涉及循环依赖的检测,以及涉及很多变量的记录存取。这个方法首先尝试从 singletonObjects里面获取实例,如果获取不到再从earlySingletonObjects 里面获取,如果还获取不到,再尝试从singletonFactories 里面获取 beanName 对应的 ObjectFactory,然后调用这个 ObjectFactory 的 getObject 来创建 bean,并放到 earlySingletonObjects里面去,并且从 singletonFacotories里面 remove掉这个 ObjectFactorγ,而对于后续的所有内存操作都只为了循环依赖检测时候使用,也就是在 allowEarlyReference为true的情况下才会使用 。
这里涉及用于存储bean的不同的map,简单解释如下 。

5.3 从bean的实例中获取对象
在 getBean方法中,getObjectForBeanlnstance是个高频率使用的方法,无论是从缓存中获得 bean 还是根据不同的scope策略加载bean。总之,得到 bean 的实例后要做的第一 步就是调用这个方法来检测一下正确性,其实就是用于检测当前 bean 是否是 FactoryBean 类型的 bean,如果是,那么需要调用该 bean 对应的 FactoryBean 实例中的 getObject()作为返回值 。
无论是从缓存中获取到的bean还是通过不同的scope策略加载的bean都只是最原始的bean状态,并不一定是最终想要的 bean。
上面的代码大多是些辅助代码以及一些功能性的判断,真正的核心代码委托给了 getObjectFromFactoryBean,getObjectForBeanInstance所做的工作:
1. 对 FactoryBean正确性的验证。
2. 对非 FactoryBean不做任何处理。
3. 对bean进行转换。
4. 将从 Factory 中解析 bean 的工作委托给 getObjectFromFactoryBean



上面已经讲述了FactoryBean的调用方法,如果bean声明为 FactoryBean 类型,则当提取 bean时提取的并不是 FactoryBean,而是FactoryBean中对应的getOject方法返回的bean, 而doGetObjectFromFactoryBean正是实现这个功能的。但是,看到在上面的方法中除了调用。object = factory.getObject()得到想要的结果后并没有直接返回,而是接下来又做了些后 处理的操作,这个又是做什么用的呢?于是跟踪进入 AbstractAutowireCapableBeanFactory 类的 postProcessObjectFromFactoryBean方法:


在 Spring获取 bean的规则中有这样一条: 尽可能保证所有 bean初始化后都会调用注册的 BeanPostProcessor 的 postProcessAfterlnitialization 方法进行处理,在实际开发过程中大可以针 对此特性设计自己的业务逻辑 。
5.4 获取单例
之前讲解了从缓存中获取单例的过程,那么, 如果缓存中不存在已经加载的单例bean就需要从头开始bean的加载过程,而Spring中使用getSingleton的重载方法实现bean的加载过程 。

上述代码中其实是使用了回调方法,使得程序可以在单例创建的前后做一些准备及处理操
作,而真正的获取单例 bean 的方法在ObjectFactory类型的实例 singletonFactory实现的。而这些准备及处理操作包括如下内容 。
- 检查缓存是否已经加载过。
- 若没有加载,则记录 beanName 的正在加载状态。
- 加载单例前记录加载状态 。beforeSingletonCreation 方法做了 一个很重要的操作:记录加载状态,也就是通过this.singletonsCurrentlylnCreation.add(beanName)将当前正要创建的bean记录在缓存中,这样便可以对循环依赖进行检测 。

- 通过调用参数传入的 ObjectFactory 的个体 Object方法实例化 bean.
- 加载单例后的处理方法调用 。当bean加载结束后需要移除缓存中对该 bean 的正在加载状态的记录 。

- 将结果记录至缓存并删除加载 bean 过程中所记录的各种辅助状态 。

- 返回处理结果 。虽然已经从外部了解了加载 bean的逻辑架构,但还并没有开始对 bean 加载
功能的探索,bean 的加载逻辑其实是在传入的 ObjectFactory 类型的参数singletonFactory 中定义的,反推参数的获取,得到如下代码 :
ObjectFactory 的核心部分其实只是调用了 createBean 的方法,所以还需要到 createBean方法中追寻真理。
5.5 准备创建 bean
在createBean函数中主要做一些准备工作 。


从代码中可以总结出函数完成的具体步骤及功能。
1.根据设置的 class 属性或者根据 classlName来解析Class 。
2.对override属性进行标记及验证.
在Spring中确实没有override-method这样的配置,但是在Spring配置中是存在lookup-method和replace-method的,而这两个配置的加载其实就是将配置统一存放在BeanDefinition 中的methodOverrides属性里,而这个函数的操作其实也就是针对于这两个配置的 。
3. 应用初始化前的后处理器,解析指定 bean 是否存在初始化前的短路操作 。
4. 创建 bean。
5.5.1 处理 override 属性
查看源码中AbstractBeanDefinition 类的 prepareMethodOveηides 方法 :

在Spring 配置中存在lookup-method和replace-method两个配置功能,而这两个配置的加载其实就是将配置统一存放在BeanDefinition中的methodOverrides属性里,这两个功能实现原理其实是在bean实例化的时候如果检测到存在 methodOverrides 属性,会动态地为当前 bean 生成代理并使用对应的拦截器为 bean 做增强处理。但是,对于方法的匹配来讲,如果一个类中存在若干个重载方法,那么,在函数调用及增强的时候还需要根据参数类型进行匹配,来最终确认当前调用的到底是哪个函数。但Spring将一部分匹配工作在这里完成了,如果当前类中的方法只有一个,那么就设置该方法没有被重载,这样在后续调用的时候便可以直接使用找到的方法,而不需要进行
方法的参数匹配验证了,而且还可以提前对方法存在性进行验证。
5.5.2 实例化的前置处理
在调用doCreate方法创建 bean实例前使用resolveBeforelnstantiation(beanName, mbd)对 BeanDefinigiton 中的属性做些前置处理。在函数中还提供了一个短路判断:
当经过前置处理后返回的结果如果不为空,那么会直接略过后续的 bean 的创建而直接返
回结果 。AOP 功能就是基于这里的判断的 。

1 . 实例化前的后处理器应用
2 . 实例化后的后处理器应用
5.6 循环依赖
5.6.1 什么是循环依赖
循环依赖就是循环引用,就是两个或多个bean相互之间的持有对方。比如 CircleA 引用CircleB, CircleB引用 CircleC, CircleC 引用 CircleA ,则它们最终反映为一个环。此处不是循
环调用,循环调用是方法之间的环调用。循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。
5.6.2 Spring 如何解决循环依赖
Spring容器循环依赖包括构造器循环依赖和setter循环依赖。在Spring 中将循环依赖的处理分成了 3 种情况 。
1.构造器循环依赖
通过构造器注入构成的循环依赖是无法解决的,只能抛出BeanCurrentlylnCreationException 异常表示循环依赖 。
2. setter循环依赖
表示通过setter注入方式构成的循环依赖 .对于Setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如 setter 注入)的bean来完成的,而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他 bean 能引用到该 bean ,如下代码所示 :

3. prototype 范围的依赖处理
对于“prototype”作用域 bean, Spring 容器无法完成依赖注入,因为 Spring 容器不进行缓
存“prototype”作用域的 bean ,因此无法提前暴露一个创建中的 bean。
对于“ singleton” 作用域 bean ,可以通过“setAllowCircularReferences(false);”来禁用循环
引用 。
5.7 创建 bean
当经历过resolveBeforelnstantiation方法后,程序有两个选择,如果创建了代理或者说重写了InstantiationAwareBeanPostProcessor 的 postProcessBeforelnstantiation 方法并在方法 postProcessBeforelnstantiation中改变了bean,则直接返回就可以了,否则需要进行常规bean的创建。而这常规bean的创建就是在doCreateBean中完成的。




整个函数的概要思路:
- 如果是单例则需要首先清除缓存 。
- 实例化bean,将BeanDefinition转换为BeanWrapper。转换是一个复杂的过程,但是可以尝试概括大致的功能,如下所示 。

- MergedBeanDefinitionPostProcessor的应用。bean 合并后的处理,Autowired注解正是通过此方法实现诸如类型的预解析。
- 依赖处理。
- 属性填充。将所有属性填充至 bean 的实例中。
- 循环依赖检查。在Sping中解决循环依赖只对单例有效,而对于prototype的bean,Spring没有好的解决办法,唯一要做的就是抛出异常。在这个步骤里面会检测已经加载的bean是否已经出现了依赖循环,并判断是再需要抛出异常 。
- 注册 DisposableBean。如果配置了destroy-method,需要注册以便于在销毁时候调用 。
- 完成创建并返回。
第 6 章 容器的功能扩展
在前面的章节中一直以BeanFacotry接口以及它的默认实现类XmlBeanFactory为例进行分析,但是Spring中还提供了另一个接口ApplicationContext,用于扩展BeanFacotry中现有的功能。
ApplicationContext和BeanFacotry两者都用于加载Bean,但相比之下ApplicationContext提供了更多的扩展功能,简单一点说:ApplicationContext包含BeanFactory的所有功能。
首先,来看看使用两个不同的类去加载配置文件在写法上的不同 。
以ClassPathXmlApplicationContext作为切人点,开始对整体功能进行分析。
设置路径是必不可少的步骤,ClassPathXmlApplicationContext中将配置文件路径以数组的方式传入,ClassPathXmlApplicationContext可以对数组进行解析并进行加载。而对于解析及功能实现都在 refresh()中实现。
6.1 设置配置路径
在 ClassPathXmlApplicationContext 中支持多个配置文件以数组方式同时传入:
此函数主要用于解析给定的路径数组。当然,如果数组中包含特殊符号如${var},那么在resolvePath中会搜寻匹配的系统变量并替换。
6.2 扩展功能
设置了路径后,便可以根据路径做配置文件的解析以及各种功能的实现了。可以说refresh函数中包含了几乎ApplicationContext提供的全部功能。


概括一下ClassPathXmlApplicationContext初始化步骤,并从中解释一下它提供的功能。
1. 初始化前的准备工作,例如对系统属性或者环境变量进行准备及验证。
2. 初始化BeanFactory,并进行XML文件读取。
这一步骤中将会复用 BeanFactory 中的配置文件读取解析及其他功能,这步之后ClassPathXmlApplicationContext 实际上就已 经包含了BeanFactory所提供的功能,也就是可以进行bean的提取等基础操作了。
3. 对BeanFactory进行各种功能填充。例如@Qualifier 与@Autowired这两个注解正是在这一步骤中增加的支持。
4. 子类覆盖方法做额外的处理 。
5. 激活各种BeanFactory处理器。
6. 注册拦截bean创建的bean处理器,这里只是注册,真正的调用是在getBean时候。
7. 为上下文初始化Message 源,即对不同语言 的消息体进行国际化处理 。
8. 初始化应用消息广播器,并放入"applicationEventMulticaster"bean 中 。
9. 留给子类来初始化其他的 bean 。
10. 在所有注册的 bean中查找listener bean,注册到消息广播器中。
11. 初始化剩下的单实例(非惰性的)。
12. 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人 。
6.3 环境准备
prepareRefresh函数主要是做些准备工作,例如对系统属性及环境变量的初始化及验证。

6.4 加载BeanFactory
obtainFreshBeanFactory方法是获取BeanFactory,经过这个函数后ApplicationContext就已经拥有BeanFactory的全部功能。

6.4.1 定制 BeanFactory
这里已经开始对 BeanFactory 的扩展,在基本容器的基础上,增加了是否允许覆盖是否
允许扩展的设置并提供了注解@Qualifier 和@Autowired 的支持。
对于允许覆盖和允许依赖的设置这里只是判断了是否为空,如果不为空要进行设置,但是并没有看到在哪里进行设置,究竟这个设置是在哪里进行设置的呢?还是那句话,使用子类覆
盖方法, 例如:
对于定制BeanFactory,Spring还提供了另外一个重要的扩展,就是设置AutowireCandidateResolver,在bean加载部分中讲解创建Bean时,如果采用autowireByType方式注入,那么默认会使用Spring 提供的SimpleAutowireCandidateResolver,而对于默认的实现并没有过多的逻辑处理。Spring使用QualifierAnnotationAutowireCandidateResolver,设置了这个解析
器后Spring 就可以支持注解方式的注入了 。
根据类型自定注人的时候,解析autowire类型时首先会调用方法 :
Object value = getAutowireCandidateResolver().getSuggestedValue (descriptor );
在QualifierAnnotationAutowireCandidateResolver中一定会提供了解析Qualifier与Autowire注解的方法。
6.4.2 加载 BeanDefinition

在初始化了 DefaultListableBeanFactory 和 XmlBeanDefinitionReader 后就可以进行配置文件
的读取了。
使用 XmlBeanDefinitionReader 的 loadBeanDefinitions 方法进行配置文件的加载机注册完全就是开始 BeanFactory 的套路。因为在XmlBeanDefinitionReader 中已经将之前初始化的 DefaultListableBeanFactory 注册进去了,所以 XmlBeanDefinitionReader 所读取的 BeanDefinitionHolder 都会注册到 DefaultListableBeanFactory 中,也就是经过此步骤,类型
DefaultListableBeanFactory 的变量 beanFactory 已经包含了所有解析好的配置 。
6.5 功能扩展
进入函数 prepareBeanFactory前,Spring已经完成对配置的解析,而ApplicationContext在功能上的扩展也由此展开 。
![]()

上面函数中主要进行了几个方面的扩展。
6.5.1 增加SpEL语言的支持
Spring表达式语言全称为Spring Expression Language,缩写为SpEL,能在运行时构建复杂表达式、存取对象图属性、对象方法调用等,并且能与 Spring 功能完美整合,比如能用来配置bean定义。SpEL是单独模块,只依赖于core模块,不依赖于其他模块,可以单独使用 。
SpEL使用#{……} 作为定界符,所有在大框号中的字符都将被认为是SpEL,使用格式如下:
在源码中通过代码beanFactory.setBeanExpressionResoIver(new StandardBeanExpressionResolver())注册语言解析器,就可以对SpEL进行解析。
Spring在bean进行初始化时会有属性填充的一步,而在这一步中Spring会调用AbstractAutowireCapableBeanFactory类的applyPropertyValues函数来完成功能。就在这个函数中,会通过构造BeanDefinitionValueResolver类型实例valueResolver来进行属性值的解析。同时,也是在这个步骤中一般通过AbstractBeanFactory中的evaluateBeanDefinitionString方法去完成SpEL 的解析 。
6.5.2 增加属性注册编辑器
在 Spring DI 注入的时候可以把普通属性注入进来,但像Date类型就无法被识别。
上面代码中, 需要对日期型属性进行注入:
如果直接这样使用,程序则会报异常,类型转换不成功。因为在UserManager中的dataValue属性是Date类型的,而在XML中配置的却是String 类型的,所以当然会报异常 。
Spring 针对此问题提供了两种解决办法 。
- 使用自定义属性编辑器

- 注册 Spring 自带的属性编辑器 CustomDateEditor

6.5.3 添加ApplicationContextAwareProcessor处理器
继续通过AbstractApplicationContext的prepareBeanFactory方法的主线来进行函数跟踪。对于beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this))其实主要目的就是注册个 BneaPostProcessor,而真正的逻辑还是在ApplicationContextAwareProcessor中。
ApplicationContextAwareProcessor实现BeanPostProcessor接口,回顾下之前在bean实例化的时候,也就是Spring激活bean的init-method的前后,会调用BeanPostProcessor的postProcessBeforelnitialization方法和postProcessAfterlnitialization 方法。对于ApplicationContextAwareProcessor也关心这两个方法。
对于postProcessAfterlnitialization 方法,在 ApplicationContextAwareProcessor 中并没有做过多逻辑处理。 
重点看一下 postProcessBeforelnitialization 方法。![]()


postProcessBeforelnitialization 方法中调用了invokeAwarelnterfaces。从invokeAwareinterfaces方法中,或许已经或多或少了解了 Spring 的用意,实现这些Aware接口的bean在被初始化之后,可以取得一些对应的资源 。
6.5.4 设置忽略依赖
当Spring将ApplicationContextAwareProcessor注册后,那么在invokeAwarelnterfaces方法中间接调用的Aware类已经不是普通的bean,如ResourceLoaderAware、ApplicationEventPublisherAware等,那么当然需要在Spring做bean的依赖注入的时候忽略它们。而ignoreDependencyInterface的作用正是在此 。
6.5.5 注册依赖

6.6 BeanFactory 的后处理
BeanFacotry作为Spring中容器功能的基础,用于存放所有已经加载的bean,为了保证程序上的高可扩展性,Spring针对 BeanFactory做了大量的扩展,比如熟知的PostProcessor等都是在这里实现的 。
6.6.1 激活注册的BeanFactoryPostProcessor
正式开始介绍之前先了解下 BeanFactoryPostProcessor 的用法。
BeanFactoryPostProcessor接口跟BeanPostProcessor类似,可对bean的定义(配置元数
据)进行处理,即Spring IoC容器允许BeanFactoryPostProcessor在容器实际实例化任何其他的bean之前读取配置元数据,并有可能修改它。也可以配置多个BeanFactoryPostProcessor。还能通过设置“order”属性来控制BeanFactoryPostProcessor的执行次序(仅当BeanFactoryPostProcessor 实现了Ordered 接口时才可以设置此属性)。
如果想改变实际的bean实例,最好使用BeanPostProcessor,BeanFactoryPostProcessor的作用域范围是容器级的。如果在容器中定义一个BeanFactoryPostProcessor,它仅仅对此容器中的bean进行后置处理。BeanFactoryPostProcessor不会对定义在另 一个容器中的bean进行后置处
理,即使这两个容器都是在同一层次上。在Spring中存在对于BeanFactoryPostProcessor的典型应用,比如PropertyPlaceholderConfigurer。
1. BeanFactoryPostProcessor的典型应用:PropertyPlaceholderConfigurer
有时阅读Spring的Bean描述文件时,也许会遇到类似如下的一些配置:
其中变量引用${bean.message},这就是Spring的分散配置,可以在另外的配置文件中为 bean.message 指定值。如:![]()
当访问名为message 的 bean 时,mes属性就会被置为字符串 “ Hi,can you find me?”,但Spring 框架是怎么知道存在这样的配置文件呢 ? 这就要靠PropertyPlaceholderConfigurer 这个类的 bean:
PropertyPlaceholderConfigurer间接继承了BeanFactoryPostProcessor接口,当 Spring加载任何实现了这个接口的 bean 的配置时,都会在bean工厂载入所有 bean 的配置之后执行postProcessBeanFactory方法。在PropertyResourceConfigurer 类 中 实 现 了postProcessBeanFactory方法,在方法中先后调用了mergeProperties、convertProperties、processProperties这3个方法,分别得到配置,将得到的配置转换为合适的类型,最后将配置内容告知 BeanFactory 。
2. 使用自定义BeanFactoryPostProcessor
3. 激活 BeanFactoryPostProcessor
了解了BeanFactoryPostProcessor的用法后便可以深入研究BeanFactoryPostProcessor的调用过程。



从上面的方法中可看到,对于BeanFactoryPostProcessor的处理主要分两种情况进行,一是对于BeanDefinitionRegistry类的特殊处理,另一种是对普通的BeanFactoryPostProcessor进行处 理。 而对于每种情况都需要考虑硬编码注入注册的后处理器以及通过配置注入的后处理器。
对于BeanDefinitionRegistry类型的处理类的处理主要包括以下内容 。
- 对于硬编码注册的后处理器的处理,通过AbstractApplicationContext中的添加处理器方法 addBeanFactoryPostProcessor进行添加 。
添加后的后处理器会存放在beanFactoryPostProcessors中,而在处理BeanFactoryPostProcessor时候会首先检测beanFactoryPostProcessors是否有数据。当然 BeanDefinitionRegistryPostProcessor继承自BeanFactoryPostProcessor,不但有BeanFactoryPostProcessor的特性,同时还有自己定义 的个性化方法,也需要在此调用。所以这里需要从beanFactoryPostProcessors中挑出 BeanDefinitionRegistryPostProcessor的后处理器,并进行其postProcessBeanDefinitionRegistry方法的激活。 -
记录后处理器主要使用了 3个 List完成。

- 对以上所记录的List中的后处理器进行统一调用BeanFactoryPostProcessor的postProcessBeanFactory方法。
- 对beanFactoryPostProcessors中非BeanDefinitionRegistryPostProcessor类型的后处理器进行统一的BeanFactoryPostProcessor的postProcessBeanFactory方法调用。
- 普通 beanFactory处理。
6.6.2 注册BeanPostProcessor
上文中提到了BeanFacotoryPostProcessors的调用,现在来探索下BeanPostProcessor, 但这里并不是调用,而是注册。真正的调用其实是在 bean 的实例化阶段进行的。Spring中大部分功能都是通过后处理器的方式进行扩展的,这是Spring框架的一个特性,但是在BeanFactory中其实并没有实现后处理器的自动注册,所以在调用的时候如果没有进行手动注册其实是不能使用的。但是
在ApplicationContext 中添加了 自动注册功能,这个特性是在registerBeanPostProcessors方法中完成的。


对于 registerBeanPostProcessors 方法的实现方式 。
6.6.3 初始化消息资源
Java通过java.util.Locale类表示一个本地化对象,它允许通过语言参数和国家/地区参数创建一个确定的本地化对象。
JDK的java.util包中提供了几个支持本地化的格式化操作工具类:NumberFormat、DateFormat、MessageFormat,而在Spring中的国际化资源操作也无非是对于这些类的封装操
作,仅仅介绍下 MessageFormat 的用法以帮助大家回顾 :
Spring定义了访问国际化信息的MessageSource接口,并提供了几个易用的实现类。MessageSource分别被HierarchicalMessageSource和ApplicationContext接口扩展。
HierarchicalMessageSource接口最重要的两个实现类是ResourceBundIeMessageSource和
ReloadableResourceBundleMessageSource,它们基于Java的ResourceBundle基础类实现,允许仅通过资源名加载国际化资源。ReloadableResourceBundleMessageSource提供了定时刷新功能,允许在不重启系统的情况下更新资源的信息。StaticMessageSource主要用于程序测试,它允
许通过编程的方式提供国际化信息。DelegatingMessageSource是为方便操作父MessageSource
而提供的代理类 。
仅仅举例 ResourceBundleMessageSource的实现方式 。
1 . 定义资源文件
2. 定义配置文件![]()

其中Bean的 id必须命名为messageSource,否则会抛出 NoSuchMessageException 异常。
3 . 使用 ApplicationContext 访问国际化信息
在 initMessageSource 中的方法主要功能是提取配置中定义的messageSource,并将其记录在Spring的容器中,也就是AbstractApplicationContext中。如果用户未设置资源文件的话,Spring中也提供了默认的配置 DelegatingMessageSource 。
在initMessageSource中获取自定义资源文件的方式为beanFactory.getBean(MESSAGE_SOURCE BEAN_NAME, MessageSource.class),在这里Spring使用了硬编码的方式硬性规定了子定义资源文件必须为message,否则便会获取不到自定义资源配置,这也是为什么之前提到Bean的id如果不为message会抛出异常 。

通过读取并将自定义资源文件配置记录在容器中,那么就可以在获取资源文件的时候直接使用。例如,在AbstractApplicationContext中的获取资源文件属性的方法 : 
其中getMessageSource() 方法正是获取了之前定义的自定义资源配置 。
6.6.4 初始化ApplicationEventMulticaster
在讲解 Spring的事件传播器之前,先来看一下Spring 的事件监听器的简单用法。

initApplicationEventMulticaster的方式比较简单,无非考虑两种情况 。
作为广播器一定是用于存放监听器并在合适的时候调用监听器,不妨进入默认的广播器实现 SimpleApplicationEventMulticaster 来一探究竟,其中的一段代码是我们感兴趣的。 
当产生Spring事件的时候会默认使用SimpleApplicationEventMulticaster的multicastEvent来广播事件,遍历所有监昕器,并使用监听器中的onApplicationEvent 方法来进行监听器的处理。而对于每个监听器来说其实都可以获取到产生的事件,但是是否进行处理则由事件监听器来决定。
6.6.5 注册监听器

6.7 初始化非延迟加载单例
完成BeanFactory的初始化工作,其中包括ConversionService的设置、配置冻结及非延迟加载的bean的初始化工作。

1. ConversionService 的设置
之前提到过使用自定义类型转换器从 String 转换为 Date 的方式。还有另一种转换方式: 使用Converter。 简单示例来了解下 Converter 的使用方式。


2. 冻结配置
冻结所有的 bean定义,说明注册的 bean定义将不被修改或进行任何进一步的处理。

3. 初始化非延迟加载
ApplicationContext 实现的默认行为是在启动时将所有单例 bean 提前进行实例化,这意味着作为初始化过程的一部分,这个实例化的过程就是在 finishBeanFactorylnitialization 中完成的。

6.8 finishRefresh
在Spring中提供了Lifecycle接口, Lifecycle中包含 start/stop方法,实现此接口后 Spring会保证在启动的时候调用其 start方法开始生命周期,并在 Spring关闭的时候调用 stop方法来结束生命周期,通常用来配置后台程序,在启动后一直运行(如对 MQ 进行轮询等)。 而 ApplicationContext 的初始化最后正是保证了这一功能的实现。

1. initlifecycleProcessor
当ApplicationContext启动或停止时,它会通过 LifecycleProcessor来与所有声明的bean的周期做状态更新,而在 LifecycleProcessor的使用前首先需要初始化。
2. onRefresh
启动所有实现了 Lifecycle接口的 bean。

3. publishEvent
当完成 ApplicationContext初始化的时候,要通过 Spring 中的事件发布机制来发出 Context RefreshedEvent 事件,以保证对应的监听器可以做进一步的逻辑处理.
第7章 AOP
Spring2.0 采用@AspectJ 注解,对POJO进行标注,从而定义一个包含切点信息和增强横切逻辑的切面。Spring 2.0 可以将这个切面织入到匹配的目标Bean中。@AspectJ注解使用AspectJ
切点表达式语法进行切点定义,可以通过切点函数、运算符、通配符等高级功能进行切点定义,
拥有强大的连接点描述能力。
1.1 动态 AOP 使用示例
1. 创建用于拦截的 bean


2. 创建Advisor 
3. 创建配置文件
要在 Spring中开启 AOP功能,还需 要在配置文件中作如下声明: 
4. 测试 
7.2 动态AOP自定义标签
全局搜索后发现在 AopNamespaceHandler 中对应着这样一段函数: 
在解析配置文件时,一旦遇到aspectj-autoproxy 注解时就会使用解析器AspectJAutoProxyBeanDefinitionParser 进行解析,现在看一下AspectJAutoProxyBean- DefinitionParser 的内部实现 。
7.2.1 注册 AnnotationAwareAspectJAutoProxyCreator
所有解析器,因为是对BeanDefinitionParser接口的统一实现,入口都是从 parse 函数开始的,AspectJAutoProxyBeanDefinitionParser 的 parse 函数如下:

在registerAspectJAnnotationAutoProxyCreatorifNecessary 方法中主要完成了3件事情,基本上每行代码就是一个完整的逻辑。
1 . 注册或者升级 AnnotationAwareAspectJAutoProxyCreator
AOP 的实现基本上都是靠 AnnotationAwareAspectJAutoProxyCreator去完成,它可以根据@Point 注解定义的切点来自动代理相匹配的 bean。但是为了配置简便,Spring使用了自定义配置来帮助自动注册 AnnotationAwareAspectJAutoProxyCreator,其注册过程就是在这里实现的。

2. 处理 Proxy-target-class 以及 expose-proxy 属性
useClassProxyinglfNecessary实现了 proxy-target-class属性以及 expose”proxy属性的处理。


7.3 创建AOP代理
AnnotationAwareAspectJAutoProxyCreator实现了BeanPostProcessor接口,而实现BeanPostProcessor后,当Spring 加载这个 Bean 时会在实例化前调用其 postProcessAfterrlnitialization方法 ,而对于 AOP 逻辑的分析也由此开始。
在父类 AbstractAutoProxyCreator 的 postProcessAfterlnitialization 中代码如下:

真正创建代理的代码是从getAdvicesAndAdvisorsForBean 开始的,创建代理主要包含了两个步骤:
- 获取增强方法或者增强器.
- 根据获取的增强进行代理 。

对于指定 bean 的增强方法的获取一定是包含两个步骤的,获取所有的增强以及寻找所有增强中适用于 bean 的增强并应用,那么 findCandidateAdvisors与 findAdvisorsThatCanApply便是做了这两件事情。当然,如果无法找到对应的增强器便返回 DO_NOT_PROXY,其中 DO_NOT_PROXY=null。
第 8 章 数据库连接 JDBC
JDBC (Java Data Base Connectivity, Java数据库连接)是一种用于执行 SQL 语句的 JavaAPI, 可以为多种关系数据库提供统一访问,它由一组用 Java语言编写的类和接口组成。
JDBC 连接数据库的流程及其原理如下:
- 在开发环境中加载指定数据库的驱动程序。
- 在 Java程序中加载驱动程序。例如加载 MySQL 的数据驱动程序的代码为 Class.forName(”com.mysql.jdbc.Driver”)。
-
创建数据连接对象。

-
创建 Staterment对象。

-
调用 Statement对象的相关方法执行相对应的 SQL语句。

-
关闭数据库连接。
8.1 Spring 连接数据库程序实现( JDBC )
Spring 对JDBC做了大量封装,消除了冗余代码,使得开发量大大减小。下面通过一个例子认识 Spring 中的 JDBC操作。



8.2 save/update 功能的实现
jdbcTemplate作为源码分析的切人点,看看它是如何实现又是如何被初始化的。
对于保存一个实体类来讲,在操作中我们只需要提供 SQL 语句以及语句中对应的参数和参数类型,其他操作便可以交由 Spring来完成了,这些工作到底包括什么呢?进入jdbcTemplate中的 update方法。
进入update方法后,先使用ArgTypePreparedStatementSetter对参数与参数类型进行封装,同时又使用 SimplePreparedStatementCreator对 SQL语句进行封装。
经过了数据封装后便可以进入 了核心的数据处理代码。
execute方法是最基础的操作,而其他操作比如 update、 query等方法则是传人不同的PreparedStatementCallback参数来执行不同的逻辑。
8.2.1 基础方法 execute
execute作为数据库操作的核心人 口,将大多数数据库操作相同的步骤统一封装,而将个性化的操作使用参数 PreparedStatementCallback进行回调。
8.2.2 Update 中的回调函数
PreparedStatementCallback作为一个接口,其中只有一个函数 dolnPreparedStatement,这个函数是用于调用通用方法 execute 的时候无法处理的一些个性化处理方法,在 update 中的函数实现:
8.3 query功能的实现

跟踪 jdbcTemplate 中的 query方法。

可以看到整体套路与 update 差不多 的,只不过在回调类 PreparedStatementCallback 的实现 中使用的是 ps.executeQuery()执行查询操作,而且在返回方法上也做了一些额外的处理。
8.4 queryForObiect
Spring中不仅仅为我们提供了 query方法,还在此基础上做了封装,提供了不同类型的 query 方法,如图 8-1所示。
第9章 整合MyBatis
MyBatis是支持普通 SQL查询、存储过程和高级映射的优秀持久层框架。 MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis使用简单的XML或注解用于配置和原始映射,将接口和Java 的 POJOs ( Plain Old Java O均ects,普通的 Java对象) 映射成数据库中的记录 。
9.1 MyBatis 独立使用
尽管我们接触更多的是 MyBatis 与 Spring 的整合使用,但是 MyBatis有它自己的独立使用方法,了解其独立使用的方法套路对分析 Spring 整合 MyBatis 非常有帮助,因为 Spring 无非 就是将这些功能进行封装以简化我们的开发流程。 MyBatis独立使用包括以下几步。
1.建立 PO

2. 建立Mapper

3. 建立配置文件
MyBatis 中的配置文件主要封装在 configuration 中,配置文件的基本结构如图 9-1所示。

4. 建立映射文件
对应于 MyBaits 全局配置中的 mappers 配置属性,主要用于建立对应数据库操作接口的SQL 映射。MyBatis 会将这里设定的 SQL 与对应的 Java 接 口相关联,以保证在 MyBatis 中调用接口的时候会到数据库中执行相应的 SQL来简化开发。

5. 建立测试类 

9.2 Spring 整合 MvBatis
1. Spring 配置文件
2. MyBatis 配置文件 
3. 映射文件 (保持不变) 
4. 测试 
9.3 源码分析
9.3.1 sqlSessionFactory 创建
Spring通过org.mybatis.Spring.SqISessionFactoryBean 封装了 MyBatis 中的实现。


1. SqlSessionFactoryBean 的初始化
此函数主要目的是对于 sqlSessionFactory 的初始化。



2. 获取 SqlSessionFactoryBean 实例
由于SqlSessionFactoryBean 实现了FactoryBean 接口,所以当通过 getBean 方法获取对应实例时,其实是获取该类的getObject()函数返回的实例,也就是获取初始化后的sqISessionFactory 属性 。
9.3.2 MapperFactoryBean 的创建
为了使用 MyBatis 功能,示例中的 Spring 配置文件提供了两个 bean,一个是 SqISssionFactoryBean类型的 bean,还有一个是 MapperFactoryBean 类型 的 bean。
结合两个测试用例综合分析,对于单独使用 MyBatis 的时候调用数据库接口的方式是:
在这一过程中,其实是 MyBatis 在获取映射的过程中根据配置信息为 UserMapper 类型动态创建了代理类。 而对于 Spring的创建方式: 
Spring 中获取的名为 userMapper 的 bean, 其实是与单独使用 MyBatis 完成了一样的功 能,那么可以推断,在 bean 的创建过程中一定是使用了 MyBatis 中的原生方法 sqlSession.getMapper(UserMapper.class)进行了再一次封装。 结合配置文件,把分析目标转向 org.mybatis.Spring.mapper.MapperFactoryBean,初步推测其中的逻辑应该在此类中实现。

在实现的接口中发现了感兴趣的两个接口 InitializingBean 与 FactoryBean。
1. MapperFactoryBean 的初始化
因为实现了InitializingBean接口,Spring会保证在bean初始化时首先调用afterPropertiesSet方法来完成其初始化逻辑。 追踪父类,发现afterPropertiesSet方法是在DaoSupport类中实现,代码如下 :
MapperFactoryBean 的初始化包括对 DAO 配置的验证及对DAO的初始工作,其中 initDao()方法是模板方法,设计为留给子类做进一步逻辑处理。而checkDaoConfig()才是分析的重点。

读过MyBatis源码就会知道,在 MyBatis实现过程中并没有于动调用configuration.addMapper方法,而是在映射文件读取过程中一旦解析到如<mapper namespace="Mapper.UserMapper”),便会自动进行类型映射的注册 。
2. 获取 MapperFactoryBean 的实例
由于 MapperFactoryBean实现了 FactoryBean接口, 所以当通过 getBean方法获取对应实例的时候其实是获取该类的 getObject()函数返回的实例 。
9.3.3 MapperScannerConfigurer
在applicationContext.xrnl中配置了userMapper供需要时使用。但如果需要用到的映射器较多的话,采用这种配置方式就显得很低效。为了解决这个问题,可以使用MapperScannerConfigurer,让它扫描特定的包,自动成批地创建映射器。 比如我们将 applicationContext.xrnl 文件中的配置改成如下:

在上面的配置中屏蔽掉了原始的代码( userMapper 的创建)而增加了 MapperScannerConfigurer 的配置, basePackage 属性是设置基本的包路径,可以使用分号或逗号作为分隔符设置多于一个的包路径。 每个映射器将会在指定的包路径中递归地被搜索到。被发现的映射器将会使用 Spring 对自动侦测组件默认的命名策略来命名。 也就是说,如果没有发现注解,它就会使用映射器的非大写的非完全限定类名。但是如果发现了@Component 或 JSR-330@Named 注解,它会获取名称。
第10章 事务
10.1 JDBC 方式下的事务使用示例
10.2 事务自定义标签
在配置文件中有一个配置 : <tx:annotation-driven />,是事务的开关,如果没有此处配置,那么 Spring中将不存在事务的功能。在 TxNamespaceHandler中的 init方法中:
Spring都会使用AnnotationDrivenBeanDefinitionParser类的 parse方法进行解析。
在解析中存在对于 mode属性的判断,根据代码,如果需要使用 AspectJ 的方式进行事务切入( Spring 中的事务是以 AOP 为基础的 ),那么可以使用这样的配置 : 
10.2.1 注册 InfrastructureAdvisorAutoProxyCreator
10.2.2 获取对应 class/method 的增强器
10.3 事务增强器
第 11 章 SpringMVC
Spring 的 MVC 是基于 Servlet功能实现的,通过实现 Servlet接口的 DispatcherServlet来封 装其核心功能实现,通过将请求分派给处理程序,同时带有可配置的处理程序映射、视图解析、 本地语言、主题解析以及上载文件支持 。 默认的处理程序是非常简单的 Controller 接口,只有 一个方法 ModelAndView handleRequest(request, response)。 Spring 提供一个控制器层次构,可以派生子类。 如果应用程序需要处理用户输入表单, 那么可以继承 AbstractFormController。 如果需要把多页输入处理到一个表单 ,那么可以继承 AbstractWizardFormController。
11.1 SpringMVC 快速体验
1. 配置web.xml
web.xrnl文件用来初始化配置信息,比如 Welcome页面、servlet、servlet-mapping、 filter、 listener、 启动加载级别等。但是,SpringMVC 的实现原理是通过 servlet拦截所有Url来达到控制的目的。
2. 创建 Spring 配置文件 applicationContext.xml


Intema!ResourceViewResolver是一个辅助 bean,会在 ModelAndView返回的视图名前加上 prefix 指定的前缀,再在最后加上 suffix指定的后缀 , 例如:由于 XX.Controller返回的 ModelAndView 中 的视图名是 testview,故该视图解析器将在WEB-INF/jsp/testview.jsp处查找视图 。
3. 创建 model
模型创建主要的目的就是承载数据,使数据传输更加方便。
4. 创建 controller
控制器用于处理 Web请求, 每个控制器都对应着一个逻辑处理。
ModelAndView 对象保存了 视图以及视图显示的模型数据,例如其中的参数如下。
5. 创建视图文件 userlist.jsp

6. 创建 Serviet配置文件 Spring-servlet.xml

因为 SpringMVC 是基于 Servlet 的实现,所以在 Web 启动的时候 ,服务器会首先尝试加载 对应于 Servlet 的配置文件,而为了让项目更加模块化,通常我们将 Web 部分的配置都存放于 此配置文件中 。
至 此,已 经完 成了 SpringMVC 的搭建 ,启动服务器,输入网址 http://localhost:8080/ Springrnvc/userlist.htm
11.2 Contextloaderlistener
当使用编程方式的时候我们可以直接将 Spring自己置信息作为参数传人 Spring容器中,如 
在Web环境下,通常是将路径以context-param 的方式注册并使用ContextLoaderListener进行监昕读取。
ContextLoaderListener 的作用就是启动 Web 容器时,自动装配 ApplicationContext 的配置信 息。 因为它实现了 ServletContextListener这个接口,在 web.xml 配置这个监昕器,启动容器时, 就会默认执行它实现的方法。ServletContext对象在应用启动时被创建, 在应用关闭的时候被销毁。 ServletContext在全局范围内有效,类似于应用中的一个全局变量。
在 ServletContextListener 中的核心逻辑便是初始化 WebApplicationContext 实例并存放至 ServletContext 中 。
11.2.1 ServletContextlistener 的使用
11.2.2 Spring 中的 ContextloaderListener
ServletContext启动之后会调用 ServletContextListener的 contextlnitialized方法:
initWebApplicationContext 函数主要是体现了创建 WebApplicationContext 实例的一个功能 架构,从函数中我们看到了初始化的大致步骤。
1. WebApplicationContext存在性的验证。
2 创建 WebApplicationContext实例。
3. 将实例记录在 servletContext 中。
4. 映射当前的类加载器与创建的实例到全局变量 currentContextPerThread 中。
11.3 DispatcherServlet
在 Spring 中, ContextLoaderListener 只是辅助功能,用于创建 WebApplicationContext类型实例, 而真正的逻辑实现其实是在DispatcherServlet中进行的。servlet 的生命周期是由 servlet的容器来控制的,它可以分为 3个阶段:初始化、运行和销毁。
11.3.1 servlet 的使用
11.3.2 DispatcherServlet 的初始化
11.3.3 WebApplicationContext 的初始化
initWebApplicationContext 函数的主要工作就是创建或刷新 WebApplicationContext 实例并对 servlet功能所使用的变量进行初始化。
本文详细介绍了Spring的核心架构,包括Core Container、Data Access/Integration、Web、AOP和Test模块。重点讲解了Spring的BeanFactory和ApplicationContext,以及容器的基本实现,如bean的定义、配置文件读取、容器的结构组成和核心类。同时,文章通过源码分析,探讨了XmlBeanFactory的初始化、bean的加载和容器扩展功能,涉及配置文件封装、资源加载和注册BeanDefinitions的过程。




752

被折叠的 条评论
为什么被折叠?



