Spring源码分析基本介绍
Spring源码分析(一)基本介绍
摘要:本文结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码。若有描述错误之处,欢迎指正。
前言
作为一名开发人员,阅读源码是一个很好的学习方式。本文将结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码,若有描述错误之处,欢迎指正。
Spring是2003年兴起的一个轻量级Java开源框架,旨在解决企业应用开发的复杂性。Spring发展至今,衍生出非常丰富的模块,并应用在多种场景,比如:桌面应用,Web应用等。Spring的模块化可以允许你只使用需要的模块,而不必全部引入。
目录
一、整体架构
1. 核心容器
2. 数据访问/集成
3. Web
4. AOP
5. Test
二、设计理念
三、使用场景
1. 典型的Spring web应用程序
2. Spring中间层使用第三方web框架
3. 远程调用
4. EJBs-包装现存POJOs
一、整体架构
Spring框架是一个分层架构,他包含一系列的功能要素,并被分为大约20个模块,如下图所示(很遗憾,并没有找到Spring5的架构图,下图是Spring4的,但结合Spring5的源码来看,该图还是能够体现Spring5的核心模块)
这些模块被总结为以下几部分。
1. 核心容器
Core Container(核心容器)包含有Core、Beans、Context和Expression Language模块。Core和Beans模块是框架的基础部分,提供IoC(控制反转)和DI(依赖注入)特性。这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置。
- Core模块主要包含Spring框架基本的核心工具类,Spring的其他组件都要使用到这个包里的类,Core模块是其他组件的基本核心。当然你也可以在自己的应用系统中使用这些工具类。
- Beans模块是所有应用都要用到的,它包含访问配置文件、创建和管理Bean以及进行Inversion of Control/Dependency Injection(IoC/DI)操作相关的所有类。
- Context模块构建于Core和Beans模块基础之上,提供了一种类似于JNDI注册器的框架式的对象访问方法。Context模块继承了Beans的特性,为Spring核心提供了大量扩展,添加了对国际化(例如资源绑定)、事件传播、资源加载和对Context的透明创建的支持。Context同时也支持J2EE的一些特性,例如EJB、JMX和基础的远程处理。ApplicationContext接口是Context模块的关键。
- Expression Language 模块提供了一个强大的表达式语言用于在运行时查询和操纵对象。它是JSP2.1规范中定义的unifed expression language的一个扩展。该语言支持设置/获取属性的值,属性的分配,方法的调用,访问数组上下文(accession the context of arrays),容器和索引器,逻辑和算数运算符,命名变量以及从Spring的IoC容器中根据名称检索对象。它也支持list投影,选择和一般的list聚合。
2. 数据访问/集成
Data Access/Integration(数据访问/集成)层包含有JDBC、ORM、OXM、JMS和Transaction模块,其中:
- JDBC模块提供了一个JDBC抽象层,它可以消除冗长的JDBC编码和解析数据库厂商特有的错误代码。这个模块包含了Spring对JDBC数据访问进行封装的所有类。
- ORM(Object Relational Mapping对象关系映射)模块为流行的对象-关系映射API,如JPA、JDO、Hibernate、iBatis等,提供了一个交互层。利用ORM封装包,可以混合使用所有Spring提供的特性进行O/R映射。如前边提到的简单声明性事务管理。
Spring框架插入了若干个ORM框架,从而提供了ORM的对象关系工具,其中包括JDO、Hibernate和iBatisSQL Map。所有这些都遵从Spring的通用事务和DAO异常层次结构。
- OXM模块提供了一个对Object/XML映射实现的抽象层,Object/XML映射实现包括JAXB、Castor、XMLBeans、JiBX和XStream。
- JMS(Java Messaging Service)模块主要包含了一些制造和消费消息的特性。
- Transaction模块支持编程和声明性的事务管理,这些事务类必须实现特定的接口,并且对所有的POJO都适用。
3. Web
Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。所以Spring框架支持与Jakarta Struts的集成。Web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。Web层包含了Web、Web-Servlet、Web-Struts和Web-Porlet模块,具体说明如下。
- Web模块提供了基础的面向Web的集成特性。例如,多文件上传,使用servlet listeners初始化IoC容器以及一个面向Web的应用上下文。它还包含Spring远程支持中Web的相关部分。
- Web-Servlet模块(web.servlet.jar)包含Spring的model-view-controller(MVC)的实现。Spring的MVC框架使得模型范围内的代码和web forms之间能够清楚地分离开来,并与Spring框架的其他特性集成在一起。
- Web-Struts模块提供了对Struts的支持,使得类在Spring应用中能够与一个典型的Struts Web层集成在一起,注意,该支持在Spring3.0中是deprecated的。
- Web-Porlet模块提供了用于Portlet环境和Web-Servlet模块的MVC的实现。
4. AOP
AOP模块提供了一个符合AOP联盟标准的面向切面编程的实现,它让你可以定义例如方法拦截器和切点。从而将逻辑代码分开,降低它们之间的耦合性。利用source-level的元数据功能,还可以将各种行为信息合并到你的代码中,这有点像.Net技术中的attribute概念。
通过配置管理特性,SpringAOP模块直接将面向切面的编程功能集成到了Spring框架中,所以可以很容易地使Spring框架管理的任何对象支持AOP。Spring AOP模块为基于Spring的应用程序中的对象提供了事务管理服务。通过使用Spring AOP,不用依赖EJB组件,就可以将声明性事务管理集成到应用程序中。
- Aspects模块提供了对AspectJ(一个面向切面的框架,它扩展了Java语言)的集成支持。
- Instrumentation模块提供了class instrumentation 支持和classloader实现,使得可以在特定的应用服务器上使用。
5. Test
Test模块支持使用JUnit和TestNG对Spring组件进行测试。
二、设计理念
Spring是面向Bean的编程(BOP:Bean Oriented Programming),Bean在Spring中才是真正的主角。Bean在Spring中作用就像Object对OOP的意义一样,没有对象的概念就像没有面向对象编程,Spring中没有Bean也就没有Spring存在的意义。Spring提供了IoC 容器通过配置文件或者注解的方式来管理对象之间的依赖关系。
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。三、使用场景
前面描述的模块使得Spring成为许多场景中的合理选择,从在资源受限设备上运行的嵌入式应用程序到使用Spring事务管理功能和Web框架集成的全面的企业应用程序。
1. 典型的Spring web应用程序
2. Spring中间层使用第三方web框架
有时情况并不允许你完全切换到一个不同的框架。Spring框架不是一个要么使用全部特性要么什么都用不了的解决方案,不强制使用其中的每个功能。现存的前端如Struts,Tapestry,JSF或其他UI框架都可以同基于Spring的中间层整合在一起,从而使你能够使用Spring事务功能。你只需要使用ApplicationContext连接你的业务逻辑以及通过WebApplicationContext整合你的web层。
3. 远程调用
你可以使用Spring的Hessian-,Rmi-或HttpInvokerProxyFactoryBean类来通过web服务访问现存的代码。远程访问现存应用程序并不困难。
4. EJBs-包装现存POJOs
Spring框架还为企业JavaBeans提供了一个访问抽象层,使你能够重用现有的POJO,并将其包装在无状态会话bean中,以便在可能需要声名式安全的可扩展,故障安全的web应用程序中使用。
Spring源码分析(二)容器基本用法
摘要:本文结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码。若有描述错误之处,欢迎指正。
在正式分析Spring源码之前,我们有必要先来回顾一下Spring中最简单的用法。尽管我相信您已经对这个例子非常熟悉了。
Bean是Spring中最核心的概念,因为Spring就像是个大水桶,而Bean就像是水桶中的水,水桶脱离了水也就没什么用处了,那么我们先看看Bean的定义。
public class MySpringBean { private String str = "mySpringBean"; public String getStr() { return str; } public void setStr(String str) { this.str = str; } }
很普通,Bean没有任何特别之处。的确,Spring的目的就是让我们的Bean能成为一个纯粹的POJO,这也是Spring所追求的。接下来看看配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="mySpringBean" class="org.cellphone.uc.MySpringBean"/> </beans>
在上面的配置中我们看到了Bean的声明方式,接下来看测试代码:
public class BeanFactoryTest { @Test public void testSimpleLoad() { BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring/spring-test.xml")); MySpringBean bean = (MySpringBean) beanFactory.getBean("mySpringBean"); Assert.assertEquals("testSimpleLoad", "mySpringBean", bean.getStr()); } }
XmlBeanFactory从Spring 3.1版本开始就被废弃了,但源码中未说明废弃的原因......
直接使用BeanFactory作为容器对于Spring的使用来说并不多见,因为在企业级的应用中大多数都会使用ApplicationContext(后续再介绍两者之间的差异),这里只是用于测试,让读者更快更好地分析Spring的内部原理。
通过上面一行简单的代码就拿到了MySpringBean实例,但这行代码在Spring中却执行了非常多的逻辑。接下来就来深入分析BeanFactory.getBean方法的实现原理。
Spring源码分析(三)容器核心类
摘要:本文结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码。若有描述错误之处,欢迎指正。
在上一篇文章中,我们熟悉了容器的基本用法。在这一篇,我们开始分析Spring的源码。但是在正式开始熟悉源码之前,有必要了解一下Spring中最核心的两个类。
1. DefaultListableBeanFactory
XmlBeanFactory继承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现,而对于XmlBeanFactory与DefaultListableBeanFactory不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取,DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口。以下是DefaultListableBeanFactory的类图:
从上面的类图中,我们可以清晰地从全局角度了解DefaultListableBeanFactory的脉络。接下来先了解一下上面类图中各个类的作用。
AliasRegistry | 定义对alias的简单增删改查等操作 |
SimpleAliasRegistry | 主要使用map作为alias的缓存,并对接口AliasRegistry进行实现 |
SingletonBeanRegistry | 定义对单例的注册及获取 |
BeanFactory | 定义获取bean及bean的各种属性 |
DefaultSingletonBeanFactory | 对接口SingletonBeanRegistry各函数的实现 |
HierarchicalBeanFactory | 继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持 |
BeanDefinitionRegistry | 定义对BeanDefinition的各种增删改操作 |
FactoryBeanRegistrySupport | 在DefaultSingletonBeanRegistry的基础上增加了对FactoryBean的特殊处理功能 |
ConfigurableBeanFactory | 提供配置Factory的各种方法 |
ListableBeanFactory | 根据各种条件获取bean的配置清单 |
AbstractBeanFactory | 综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能 |
AutowireCapableBeanFactory | 提供创建bean、自动注入,初始化以及应用bean的后处理器 |
AbstractAutowireCapableBeanFactory | 综合AbstractBeanFactory并对接口AutowireCapableBeanFactory进行实现 |
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 | 定义读取Document并注册BeanDefiniton功能 |
BeanDefinitionParserDelegate | 定义解析Element的各种方法 |
通过以上分析,我们可以梳理出整个XML配置文件读取的大致流程,如下图所示:
在XmlBeanDifinitonReader中主要包含以下几个步骤的处理:
1)通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourceLoader将资源文件路径转换为对应的Resource文件。
2)通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Document文件。
3)通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析。
Spring源码分析(四)容器的基础XmlBeanFactory
摘要:本文结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码。若有描述错误之处,欢迎指正。
经过Spring源码分析(二)容器基本用法和Spring源码分析(三)容器核心类两篇文章,我们已经对Spring的容器功能有了一个大致的了解,尽管你可能还很迷糊,但是不要紧,接下来我们会详细探索每个步骤的实现。首先要深入分析的是以下功能的代码实现:
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring/spring-test.xml"));
通过XmlBeanFactory初始化时序图,我们看下上面代码的执行逻辑:
时序图从BeanFactoryTest测试类开始,通过时序图我们可以一目了然地看到整个逻辑处理顺序。先调用了ClassPathResource的构造函数来构造Resource资源文件的实例对象,后续的资源处理就可以用Resource提供的各种服务来操作了,当我们有了Resource后就可以进行XmlBeanFactory的初始化了。那么Resource文件是如何封装的呢?
1. 配置文件封装
Spring的配置文件读取是通过ClassPathResource进行封装的,如new ClassPathResource("spring/spring-test.xml"),那么ClassPathResource完成了什么功能呢?
在Java中,将不同来源的资源抽象成URL,通过注册不同的handler(URLStreamHander)来处理不同来源的资源的读取逻辑,一般handler的类型使用不同的前缀(协议,Protocol)来识别,如“file:”、"http:"、"jar:"等,然而URL没有默认定义相对Classpath或ServletContext等资源的handler,虽然可以注册自己的URLStreamHandler来解析特定的URL前缀(协议),比如“classpath:”,然而这需要了解URL的实现机制,而且URL也没有提供一些基本的方法,如检查当前资源是否存在、检查当前资源是否可读等方法。因而Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口来封装底层资源。
public interface InputStreamSource { InputStream getInputStream() throws IOException; }
public interface Resource extends InputStreamSource { boolean exists(); default boolean isReadable() { return true; } default boolean isOpen() { return false; } default boolean isFile() { return false; } URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String relativePath) throws IOException; @Nullable String getFilename(); String getDescription(); }
InputStreamSource封装任何能返回InputStream的类,比如File、Classpath下的资源和Byte Array等。它只有一个方法定义:getInputStream(),该方法返回一个新的InputStream对象。
Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、Classpath等。首先,它定义了3个判断当前资源状态的方法:存在性(exists)、可读性(isReadable)、是否处于打开状态(isOpen)。另外,Resource接口还提供了不同资源到URL、URI、File类型的转换,以及获取lastModified属性、文件名(不带文件信息的文件名,getFilename())的方法。为了便于操作,Resource还提供了基于当前资源创建一个相对资源的方法:createRelative()。在错误处理中需要详细地打印出错的资源文件,因而Resource还提供了getDescription()方法用于在错误处理中的打印信息。
对不同来源的资源文件都有相应的Resource实现:文件(FileSystemResource)、Classpath资源(ClasspathResource)、URL资源(URLResource)、InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等。相关类图如下图所示:
在日常的开发工作中,资源文件的加载也是经常用到的,可以直接使用Spring提供的类,比如在希望加载文件时可以使用以下代码:
Resource resource = new ClassPathResource("spring/spring-test.xml.xml"); InputStream inputStream = resource.getInputStream();
得到inputStream后,我们可以按照以前的开发方式进行实现了,并且我们已经可以利用Resource及其子类为我们提供好的诸多特性。
有了Resource接口便可以对所有资源文件进行统一处理。至于实现,其实是非常简单的,以getInputStream为例,ClassPathResource中的实现方式便是通过class或者classLoader提供的底层方法进行调用,而对于FileSystemResource的实现其实更简单,直接使用FileInputStream对文件进行实例化。
ClassPathResource.java
/** * This implementation opens an InputStream for the given class path resource. * @see java.lang.ClassLoader#getResourceAsStream(String) * @see java.lang.Class#getResourceAsStream(String) */ @Override public InputStream getInputStream() throws IOException { InputStream is; if (this.clazz != null) { is = this.clazz.getResourceAsStream(this.path); } else if (this.classLoader != null) { is = this.classLoader.getResourceAsStream(this.path); } else { is = ClassLoader.getSystemResourceAsStream(this.path); } if (is == null) { throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist"); } return is; }
FileSystemResource.java
/** * This implementation opens a NIO file stream for the underlying file. * @see java.io.FileInputStream */ @Override public InputStream getInputStream() throws IOException { try { return Files.newInputStream(this.file.toPath()); } catch (NoSuchFileException ex) { throw new FileNotFoundException(ex.getMessage()); } }
当通过Resource相关类完成了对配置文件进行封装后配置文件的读取工作就全权交给XmlBeanDefinitionReader来处理了。
了解了Spring中将配置文件封装为Resource类型的实例方法后,我们就可以继续探讨XmlBeanFactory的初始化方法了,XmlBeanFactory初始化有许多方法,Spring中提供了很多的构造函数,在这里分析的是使用Resource实例作为构造函数参数的方法,代码如下:
/** * Create a new XmlBeanFactory with the given resource, * which must be parsable using DOM. * @param resource the XML resource to load bean definitions from * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource) throws BeansException { // 调用XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory)构造方法 this(resource, null); } /** * Create a new XmlBeanFactory with the given input stream, * which must be parsable using DOM. * @param resource the XML resource to load bean definitions from * @param parentBeanFactory parent bean factory * @throws BeansException in case of loading or parsing errors */ // parentBeanFactory为父类BeanFactory用于factory合并,可以为空 public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); }
上面函数的代码中,this.reader.loadBeanDefinitions(resource)才是资源加载的真正实现,也是我们分析的重点之一。我们可以看到时序图中提到的XmlBeanDefinitionReader加载数据就是这里完成的,但是在XmlBeanDefinitionReader加载数据前还有一个调用父类构造函数初始化的过程:super(parentBeanFactory),跟踪代码到父类AbstractAutowireCapableBeanfactory的构造函数中:
/** * Create a new AbstractAutowireCapableBeanFactory. */ public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
这里有必要提及下ignoreDependencyInterface方法。ignoreDependencyInterface的主要功能,是忽略给定接口的自动装配功能,那么,这样做的目的是什么呢?会产生什么样的效果呢?
举例来说,当A中有属性B,那么当Spring在获取A的Bean的如果其属性B还没有初始化,那么Spring会自动初始化B,这也是Spring提供的一个重要特性。但是,某些情况下B不会被初始化,其中的一种情况就是B实现了BeanNameAware接口。Spring中是这样介绍的,自动装配的时候,忽略给定的依赖接口,典型的应用是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者ApplicationContext通过ApplicationContextAware进行注入。
2. 加载Bean
之前提到的在XmlBeanFactory构造函数中调用了XmlBeanDefinitionReader类型的reader属性提供的方法this.reader.loadBeanDefinitions(resource),而这句代码则是整个资源加载的切入点,我们先来看看这个方法的时序图,如下图所示:
看到上图我们才知道,原来绕了这么久还没有切入正题,还一直在为加载XML文件和解析注册Bean在做准备工作。从上面的时序图中我们尝试梳理整个的处理过程如下:
- 封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource类进行封装。
- 获取输入流。从Resource中获取对应的InputStream并构造InputSource。
- 通过构造的InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。
我们来看一下loadBeanDefinitions函数具体的实现过程:
/** * Load bean definitions from the specified XML file. * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */ @Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); }
那么EncodeResource的作用是什么呢?通过名称,我们可以大致推断这个类主要是用于对资源文件的编码进行处理。其中的主要逻辑体现在getReader()方法中,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码。
/** * Open a {@code java.io.Reader} for the specified resource, using the specified * {@link #getCharset() Charset} or {@linkplain #getEncoding() encoding} * (if any). * @throws IOException if opening the Reader failed * @see #requiresReader() * @see #getInputStream() */ public Reader getReader() throws IOException { if (this.charset != null) { return new InputStreamReader(this.resource.getInputStream(), this.charset); } else if (this.encoding != null) { return new InputStreamReader(this.resource.getInputStream(), this.encoding); } else { return new InputStreamReader(this.resource.getInputStream()); } }
上面代码构造了一个有编码(encoding)的InputStreamReader。当构造好encodeResource对象后,再次转入了可复用方法loadBeanDefinitions(new EncodedResource(resource))。
这个方法内部才是真正的数据准备阶段,也就是时序图锁描述的逻辑:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isDebugEnabled()) { logger.debug("Loading XML bean definitions from " + encodedResource.getResource()); } // 通过属性来记录已经加载的资源 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { // 从encodedResource中获取已经封装的Resource对象并再次从Resource中获取其中的inputStream InputStream inputStream = encodedResource.getResource().getInputStream(); try { // InputSource这个类并不是来自于Spring,他的全路径是org.xml.sax.InputSource InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 真正进入了逻辑核心部分 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { // 关闭输入流 inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
我们再次准备一下数据准备阶段的逻辑,首先对传入的resource参数做封装,目的是考虑到Resource可能存在编码要求的情况,其次,通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据通过参数传入真正的核心处理部分doLoadBeanDefinitions(inputSource, encodedResource.getResource())。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
上面的代码只做了两件事,每一件都是必不可少的。
- 加载XML文件,并得到对应的Document。
- 根据返回的Document注册Bean信息。
这两个步骤支撑着整个Spring容器部分的实现基础,尤其是第二部对配置文件的解析,逻辑非常复杂,下一节里面先从获取Document讲起。
Spring源码分析(五)获取Document
摘要:本文结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码。若有描述错误之处,欢迎指正。
这一篇开始进行Document加载了,XmlBeanFactoryReader类对于文档读取并没有亲历亲为,而是委托给了DocumentLaoder去执行,DocumentLoader是个接口,真正调用的是DefaultDocumentLoader,解析代码如下:
/** * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured * XML parser. */ @Override public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); } DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource); }
对于这部分代码其实并没有太多可以描述的,因为通过SAX解析XML文档的套路都差不多,Spring在这里并没有什么特殊的地方,同样首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象。这里有必要提及一下EntityResolver,对于参数entityResolver,传入的是通过getEntityResolver() 函数获取的返回值,如下代码:
protected EntityResolver getEntityResolver() { if (this.entityResolver == null) { // Determine default EntityResolver to use. ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader != null) { this.entityResolver = new ResourceEntityResolver(resourceLoader); } else { this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader()); } } return this.entityResolver; }
那么,EntityResolver到底是做什么用的呢?
EntityResolver用法
在loadDocument方法中涉及一个参数EntityResolver,何为EntitiResolver?官网这样解释:如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例。也就是说,对于解析一个XML,SAX首先读取该XML文档上的声明,根据声明去寻找相应的DTD定义,以便对文档进行一个验证。默认的寻找规则,即通过网络(实现上就是声明的DTD的URL地址)来下载相应的DTD声明,并进行认证。下载的过程漫长,而且当网络中断或不可用的时候,这里会报错,就是因为相应的DTD声明没有被找到的原因。
enntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们将DTD文件放到项目中某处,在实现时直接将此文档读取并返回给SAX即可。这样就避免了通过网络来寻找相应的声明。
首先看enntityResolver的接口方法声明:
public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException;
这里,它接受两个参数publicId和systemId,并返回一个inputSource对象。这里我们以特定配置文件来进行讲解。
(1)如果我们在解析验证模式为XSD的配置文件,代码如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> ...... </beans>
读取到以下两个参数。
- publicId:null
- systemId:http://www.springframework.org/schema/beans/spring-beans.xsd
(2)如果我们在解析验证模式为DTD的配置文件,代码如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.springframework.org/schema/beans/spring-beans.xsd"> <beans"> ...... </beans>
读取到以下两个参数:
- publicId:-//Spring//DTD BEAN 2.0//EN
- systemId:http://www.springframework.org/schema/beans/spring-beans.xsd
之前已经提到过,验证文件默认的加载方式是通过URL进行网络下载,这样会造成延时,用户体验也不好,一般的做法是将验证文件放置在自己的工程里,那么怎么做才能将这个URL转换为自己工程里对应的地址文件呢?我们以加载DTD文件为例来看看Spring中是如何实现的。根据之前Spring中通过getEntityResolver()方法对EntityResolver的获取,我们知道,Spring中使用DelegatingEntityResolver类为EntityResolver的实现类,resolveEntity实现方法如下:
@Override @Nullable public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException { if (systemId != null) { if (systemId.endsWith(DTD_SUFFIX)) { // 如果是dtd从这里解析 return this.dtdResolver.resolveEntity(publicId, systemId); } else if (systemId.endsWith(XSD_SUFFIX)) { // 通过调用META-INF/Spring.schemas解析 return this.schemaResolver.resolveEntity(publicId, systemId); } } return null; }
我们可以看到,对不同的验证模式,Spring使用了不同的解析器解析。这里简单描述一下原理,比如加载DTD类型的BeanDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去当前路径下寻找,而加载XSD类型的PluggableSchemaResolver类的resolveEntity是默认到META-INF/Spring.schemas文件中找到systemId所对应的XSD文件并加载。下面是BeansDtdResolver的源码:
@Override @Nullable public InputSource resolveEntity(String publicId, @Nullable String systemId) throws IOException { if (logger.isTraceEnabled()) { logger.trace("Trying to resolve XML entity with public ID [" + publicId + "] and system ID [" + systemId + "]"); } if (systemId != null && systemId.endsWith(DTD_EXTENSION)) { int lastPathSeparator = systemId.lastIndexOf('/'); int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator); if (dtdNameStart != -1) { String dtdFile = DTD_NAME + DTD_EXTENSION; if (logger.isTraceEnabled()) { logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath"); } try { Resource resource = new ClassPathResource(dtdFile, getClass()); InputSource source = new InputSource(resource.getInputStream()); source.setPublicId(publicId); source.setSystemId(systemId); if (logger.isDebugEnabled()) { logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile); } return source; } catch (IOException ex) { if (logger.isDebugEnabled()) { logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex); } } } } // Use the default behavior -> download from website or wherever. return null; }
Spring源码分析(六)解析和注册BeanDefinitions
摘要:本文结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码。若有描述错误之处,欢迎指正。
当把文件转换为Document后,接下来的提取及注册bean就是我们的重头戏。继续上一篇的分析,当程序已经拥有XML文档文件的Document实例对象时,就会被引入XmlBeanDefinitionReader的这个方法。
/** * Register the bean definitions contained in the given DOM document. * Called by {@code loadBeanDefinitions}. * <p>Creates a new instance of the parser class and invokes * {@code registerBeanDefinitions} on it. * @param doc the DOM document * @param resource the resource descriptor (for context information) * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of parsing errors * @see #loadBeanDefinitions * @see #setDocumentReaderClass * @see BeanDefinitionDocumentReader#registerBeanDefinitions */ public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 实例化BeanDefinitionReader时候会将BeanDefinitionRegistry传入,默认使用继承自DefaultListableBeanFactory的子类 int countBefore = getRegistry().getBeanDefinitionCount(); // 加载及注册bean documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // 记录本次加载的BeanDefinition个数 return getRegistry().getBeanDefinitionCount() - countBefore; }
其中的参数doc是通过上一节loadDocument加载转换出来的。在这个方法中很好地应用了面向对象单一职责的原则,将逻辑处理委托给单一的类进行处理,而这个逻辑处理类就是BeanDefinitionDocumentReader。BeanDefinitionDocumentReader是一个接口,而实例化的工作是在createBeanDefinitionDocumentReader()中完成的,而通过此方法, BeanDefinitionDocumentReader真正的类型其实已经是DefaultBeanDefinitionDocumentReader了,进入DefaultBeanDefinitionDocumentReader后,发现这个方法的重要目的之一就是提取root,以便再次将root作为参数继续BeanDefinition的注册。
/** * This implementation parses bean definitions according to the "spring-beans" XSD * (or DTD, historically). * <p>Opens a DOM Document; then initializes the default settings * specified at the {@code <beans/>} level; then parses the contained bean definitions. */ @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
经过艰难险阻,磕磕绊绊,我们终于到了核心逻辑的底部doRegisterBeanDefinitions(root),至少我们在这个方法中看到了希望。
如果说以前一直是XML加载解析的准备阶段,那么doRegisterBeanDefinitions算是真正地开始进行解析了,我们期待的核心部分真正开始了。
/** * Register each bean definition within the given root {@code <beans/>} element. */ @SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...) protected void doRegisterBeanDefinitions(Element root) { // Any nested <beans> elements will cause recursion in this method. In // order to propagate and preserve <beans> default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. // 专门处理解析 BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { // 处理profile属性 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } /* 这里是模板方法模式 */ // 解析前处理,留给子类实现 preProcessXml(root); parseBeanDefinitions(root, this.delegate); // 解析后处理,留给子类实现 postProcessXml(root); this.delegate = parent; }
通过上面的代码我们看到了处理流程,首先是对profile的处理,然后开始进行解析,可以当我们跟进preProcessXml(root)和postProcessXml(root)发现代码是空的,既然是空的写着还有什么用呢?就像面向对象设计方法学中常说的一句话,一个类要么是面向继承设计的,要么就用final修饰。在DefaultBeanDefinitionDocumentReader中并没有用final修饰,所以它是面向继承设计的。这两个方法正是为子类而设计的,如果读者有了解过设计模式,可以很快速地反应出这是模板方法模式,如果继承自DefaultBeanDefinitionDocumentReader的子类需要在Bean解析前后做一些处理的话,那么只需要重写这两个方法就可以了。
1. profile属性的使用
我们注意到在注册bean的最开始是对PROFILE_ATTRIBUTE属性的解析,可能对于我们来说,profile属性并不是很常用。让我们先了解一下这个属性。
分析profile前我们先了解下profile的用法,官方实例代码片段如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <beans profile="dev"> ...... </beans> <beans profile="production"> ...... </beans> </beans>
集成到Web环境中时,在web.xml中加入以下代码:
<context-param> <param-name>Spring.profiles.active</param-name> <param-value>dev</param-value> </context-param>
有了这个特性我们就可以在配置文件中部署两套配置来适用于生产环境和开发环境,这样可以方便的进行切换开发、部署环境,最常用的就是更换不同的数据库。
了解了profile的使用再来分析代码会清晰很多,首先程序会获取beans节点是否定义了profile属性,如果定义了则会需要到环境变量中去寻找,所以这里首先断言environment不可能为空,因为profile是可以同时指定多个的,需要程序对其拆分,并解析每个profile是都符合环境变量中所定义的,不定义则不会浪费性能去解析。
2. 解析并注册BeanDefinition
处理了profile后就可以进行XML的读取了,跟踪代码进入parseBeanDefinitions(root, this.delegate)。
/** * 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) { // 对beans的处理 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)) { // 对bean的处理 parseDefaultElement(ele, delegate); } else { // 对bean的处理 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
上面的代码看起来逻辑还是蛮清晰的,因为在Spring的XML配置里面有两大类Bean声明,一个是默认的,如:
<bean id="mySpringBean" class="org.cellphone.uc.MySpringBean"/>
另一类就是自定义的,如:
<tx:annotation-driven/>
而这两种的读取及解析差别是非常大的,如果采用Spring默认的配置,Spring当然知道该怎么做,但是如果是自定义的,那么就需要用户实现一些接口和配置了。对于根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement(ele)方法对自定义命名空间进行解析。而判断是否默认命名空间还是自定义命名空间的办法是使用node.getNamespaceURI()获取命名空间,并与Spring中固定的命名空间http://www.springframework.org/schema/beans进行比对。如果一致则认为是默认,否则就认为是自定义。而对于默认标签解析与自定义标签解析我们将会在下一篇中进行讨论。
Spring源码分析(七)bean标签的解析及注册
摘要:本文结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码。若有描述错误之处,欢迎指正。
在上一篇中提到过Spring中的标签包括默认标签和自定义标签两种,而两种标签的用法以及解析方式存在着很大的不同。本节开始详细分析默认标签的解析过程。
默认标签的解析是在parseDefaultElement函数中进行的,函数中的功能逻辑一目了然,分别对4种不同标签(import、alias、bean和beans)做了不同的处理。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { // 对import标签的处理 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } // 对alias标签的处理 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } // 对bean标签的处理 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } // 对beans标签的处理 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
在4种标签中,对bean标签的解析最为复杂也最为重要,所以从此标签开始深入分析,如果能理解这个标签的解析过程,其他标签的解析就迎刃而解了。首先看看函数processBeanDefinition(ele, delegate)。
/** * Process the given bean element, parsing the bean definition * and registering it with the registry. */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // Register the final decorated instance. BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
乍一看,似乎一头雾水,没有以前的函数那样清晰的逻辑。大致的逻辑总结如下。
(1)首先委托BeanDefinitionDelegate类的parseBeanDefinitionElement方法进行元素解析,返回BeanDefinitionHolder类型的实例bdHolder,经过这个方法后,bdHolder实例已经包含我们配置文件中配置的各种属性了,例如class、name、id、alias之类的属性。
(2)当返回的dbHolder不为空的情况下若存在默认标签的子节点下再有自定义属性,还需要再次对自定义标签进行解析。
(3)解析完成后,需要对解析后的bdHolder进行注册,同样,注册操作委托给了BeanDefinitionReaderUtils的registerBeanDefinition方法。
(4)最后发出响应事件,通知相关的监听器,这个bean已经加载完成了。
配合时序图,可能会更容易理解。
解析BeanDefinition
下面我们就针对各个操作做具体分析。首先我们从元素解析及信息提取开始,也就是BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele),进入BeanDefinitionDelegate类的parseBeanDefinitionElement方法。
/** * Parses the supplied {@code <bean>} element. May return {@code null} * if there were errors during parse. Errors are reported to the * {@link org.springframework.beans.factory.parsing.ProblemReporter}. */ @Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return parseBeanDefinitionElement(ele, null); } /** * Parses the supplied {@code <bean>} element. May return {@code null} * if there were errors during parse. Errors are reported to the * {@link org.springframework.beans.factory.parsing.ProblemReporter}. */ @Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { // 解析id属性 String id = ele.getAttribute(ID_ATTRIBUTE); // 解析name属性 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); // 分割name属性 List<String> aliases = new ArrayList<>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isDebugEnabled()) { logger.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition != null) { if (!StringUtils.hasText(beanName)) { try { // 如果不存在beanName,那么根据Spring中提供的命名规则为当前bean生成对应的beanName if (containingBean != null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); // Register an alias for the plain bean class name, if still possible, // if the generator returned the class name plus a suffix. // This is expected for Spring 1.2/2.0 backwards compatibility. String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isDebugEnabled()) { logger.debug("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
以上便是对默认标签解析的全过程了。当然,对Spring的解析犹如洋葱剥皮一样,一层一层地进行,尽管现在只能看到对属性id以及name的解析,但是很庆幸,思路我们已经了解了。在开始对属性展开全面解析前,Spring在外层又做了一个当前层的功能架构,在当前层完成的主要工作包括如下内容。
(1)提取元素中的id以及name属性。
(2)进一步解析其他所有属性并统一封装至GenericBeanDefinition类型的实例中。
(3)如果检测到bean没有指定beanName,那么使用默认规则为此Bean生成beanName。
(4)将获取到的信息封装到BeanDefinitionHolder的实例中。
我们进一步查看步骤(2)中对标签其他属性的解析过程。
/** * Parse the bean definition itself, without regard to name or aliases. May return * {@code null} if problems occurred during the parsing of the bean definition. */ @Nullable public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, @Nullable BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); String className = null; // 解析class属性 if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } String parent = null; // 解析parent属性 if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } try { // 创建用于承载属性的AbstractBeanDefinition类型的GenericBeanDefinition AbstractBeanDefinition bd = createBeanDefinition(className, parent); // 硬编码解析默认bean的各种属性 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); // 提取description bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); // 解析元数据 parseMetaElements(ele, bd); // 解析lookup-method属性 parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); // 解析replaced-method parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); // 解析构造函数参数 parseConstructorArgElements(ele, bd); // 解析property子元素 parsePropertyElements(ele, bd); // 解析qualifier子元素 parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; } catch (ClassNotFoundException ex) { error("Bean class [" + className + "] not found", ele, ex); } catch (NoClassDefFoundError err) { error("Class that bean class [" + className + "] depends on not found", ele, err); } catch (Throwable ex) { error("Unexpected failure during bean definition parsing", ele, ex); } finally { this.parseState.pop(); } return null; }
终于,bean标签的所有属性,不论是常用的还是不常用的我们都看到了,尽管有些复杂的属性还需要进一步的解析,不过丝毫不会影响我们兴奋的心情。接下来,我们继续一些复杂标签属性的解析。
1. 创建用于承载属性的BeanDefinition
BeanDefinition是一个接口,在Spring中存在三种实现:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition。三种实现均继承了AbstractBeanDefinition,其中BeanDefinition是配置文件<bean>元素标签在容器中的内部表现形式。<bean>元素标签拥有class、scope、lazy-init等配置属性,BeanDefinition则提供了相应的beanClass、scope、lazyInit属性,BeanDefinition和<bean>属性是一一对应的。其中RootBeanDefinition是最常用的实现类,它对应一般的<bean>元素标签,GenericBeanDefinition是自2.5版本以后新加入的bean文件配置属性定义类,是一站式服务类。
在配置文件中可以定义父<bean>和子<bean>,父<bean>用RootBeanDefinition表示,而子<bean>用ChildBeanDefinition表示,而没有父<bean>的<bean>就使用RootBeanDefinition表示。AbstractBeanDefinition对两者共同的类信息进行抽象。
Spring通过BeanDefinition将配置文件中的<bean>配置信息转换为容器的内部表示,并将这些BeanDefinition注册到BeanDefinitionRegistry中。Spring容器的BeanDefinitionRegistry就像是Spring配置信息的内存数据库,主要是以map的形式保存,后续操作直接从BeanDefinitionResistry中读取配置信息。它们之间的关系如下图所示:
因此,要解析属性首先要创建用于承载属性的实例,也就是创建GenericBeanDefinition类型的实例。而代码createBeanDefinition(className, parent)的作用就是实现此功能。
/** * Create a bean definition for the given class name and parent name. * @param className the name of the bean class * @param parentName the name of the bean's parent bean * @return the newly created bean definition * @throws ClassNotFoundException if bean class resolution was attempted but failed */ protected AbstractBeanDefinition createBeanDefinition(@Nullable String className, @Nullable String parentName) throws ClassNotFoundException { return BeanDefinitionReaderUtils.createBeanDefinition( parentName, className, this.readerContext.getBeanClassLoader()); } /** * Create a new GenericBeanDefinition for the given parent name and class name, * eagerly loading the bean class if a ClassLoader has been specified. * @param parentName the name of the parent bean, if any * @param className the name of the bean class, if any * @param classLoader the ClassLoader to use for loading bean classes * (can be {@code null} to just register bean classes by name) * @return the bean definition * @throws ClassNotFoundException if the bean class could not be loaded */ public static AbstractBeanDefinition createBeanDefinition( @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException { GenericBeanDefinition bd = new GenericBeanDefinition(); // parentName可能为空 bd.setParentName(parentName); if (className != null) { if (classLoader != null) { // 如果classLoader不为空,则使用以传入的classLoader加载类对象,否则只是记录className bd.setBeanClass(ClassUtils.forName(className, classLoader)); } else { bd.setBeanClassName(className); } } return bd; }
2. 解析各种属性
当我们创建了bean信息的承载实例后,便可以进行bean信息的各种属性解析了,首先我们进入parseBeanDefinitionAttributes方法。parseBeanDefinitionAttributes方法是对element所有元素属性进行解析:
/** * Apply the attributes of the given bean element to the given bean * definition. * @param ele bean declaration element * @param beanName bean name * @param containingBean containing bean definition * @return a bean definition initialized according to the bean element attributes */ public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) { // 解析singleton属性 // 检查当前bean是否有singleton属性,有则提示应该使用scope属性,并抛出异常 if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele); } // 解析scope属性 else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { // 获取并设置scope属性值 bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE)); } else if (containingBean != null) { // Take default from containing bean in case of an inner bean definition. // 在嵌入beanDefinition情况下且没有单独指定scope属性则使用父类默认的属性 bd.setScope(containingBean.getScope()); } // 解析abstract属性 if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE))); } // 解析lazy-init属性 String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); if (DEFAULT_VALUE.equals(lazyInit)) { lazyInit = this.defaults.getLazyInit(); } // 若没有设置或设置成其他字符都会被设置为false bd.setLazyInit(TRUE_VALUE.equals(lazyInit)); // 解析autowire属性 String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); bd.setAutowireMode(getAutowireMode(autowire)); // 解析depends-on属性 if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE); bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS)); } // 解析autowire-candidate属性 String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE); if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) { String candidatePattern = this.defaults.getAutowireCandidates(); if (candidatePattern != null) { String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); } } else { bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate)); } // 解析primary属性 if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) { bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE))); } // 解析init-method属性 if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) { String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE); bd.setInitMethodName(initMethodName); } else if (this.defaults.getInitMethod() != null) { bd.setInitMethodName(this.defaults.getInitMethod()); bd.setEnforceInitMethod(false); } // 解析destroy-method属性 if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) { String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE); bd.setDestroyMethodName(destroyMethodName); } else if (this.defaults.getDestroyMethod() != null) { bd.setDestroyMethodName(this.defaults.getDestroyMethod()); bd.setEnforceDestroyMethod(false); } // 解析factory-method属性 if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) { bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE)); } // 解析factory-bean属性 if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) { bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE)); } return bd; }
我们可以清楚地看到Spring完成了对所有bean属性的解析,这些属性中有很多是我们经常使用的,同时我相信也一定会有或多或少的属性是读者不熟悉或是没有使用过的,感兴趣的读者可以查阅相关资料进一步了解每个属性。
3. 解析子元素meta
在开始解析元数据的分析前,我们先回顾下元数据meta属性的使用。
<bean id="mySpringBean" class="org.cellphone.uc.MySpringBean"> <meta key="str" value="I'm a test attribute."/> </bean>
这段代码并不会直接体现在MySpringBean的属性当中,而是一个额外的声明,当需要使用里面的信息的时候可以通过BeanDefinition的getAttribute(key)方法进行获取。
对meta属性的解析代码如下:
public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) { // 获取当前节点的所有子元素 NodeList nl = ele.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); // 提取meta if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) { Element metaElement = (Element) node; String key = metaElement.getAttribute(KEY_ATTRIBUTE); String value = metaElement.getAttribute(VALUE_ATTRIBUTE); // 使用key、value构造BeanMetadataAttribute BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value); attribute.setSource(extractSource(metaElement)); // 记录信息 attributeAccessor.addMetadataAttribute(attribute); } } }
4. 解析子元素lookup-method
同样,子元素lookup-method似乎并不是很常用,但是在某些时候它的确是非常有用的属性,通常我们称它为获取器注入。应用《Spring in Action》中的一句话:获取器注入是一种特殊的方法注入,它是把一个方法声明为返回某种类型的bean,但实际要返回的bean是在配置文件里面配置的,此方法可用在设计有些可插拔的功能上,接触程序依赖。我们看看具体的应用。
(1)首先创建一个父类:
public class User { public void showMe() { System.out.println("I am a user"); } }
(2)创建其子类并覆盖showMe方法:
public class Teacher extends User { @Override public void showMe() { System.out.println("I am a teacher"); } }
(3)创建调用方法:
public abstract class GetBeanTest { public void showMe() { this.getBean().showMe(); } public abstract User getBean(); }
(4)创建测试方法:
public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring/lookup-test.xml"); GetBeanTest getBeanTest = (GetBeanTest) context.getBean("getBeanTest"); getBeanTest.showMe(); } }
到现在为止,除了配置文件外,整个测试方法就完成了,如果之前没有接触过获取器注入的读者可能会有疑问:抽象方法还没有被实现,怎么可以直接调用呢?答案就在Spring为我们提供的获取器中,我们看看配置文件是怎么配置的。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="getBeanTest" class="org.cellphone.uc.GetBeanTest"> <lookup-method name="getBean" bean="teacher"/> </bean> <bean id="teacher" class="org.cellphone.uc.Teacher"/> </beans>
在配置文件中,我们看到了源码解析中提到的lookup-method子元素,这个配置完成的功能是动态地将teacher所代表的bean作为getBean的返回值,运行测试方法我们会看到控制台上的输出:
I am a teacher
当我们的业务变更或者在其他情况下,teacher里面的业务逻辑已经不再符合我们的业务要求,需要进行替换怎么办呢?这时我们需要增加新的逻辑类:
public class Student extends User { @Override public void showMe() { System.out.println("I am a student"); } }
同时修改配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="getBeanTest" class="org.cellphone.uc.GetBeanTest"> <lookup-method name="getBean" bean="student"/> </bean> <bean id="teacher" class="org.cellphone.uc.Teacher"/> <bean id="student" class="org.cellphone.uc.Student"/> </beans>
再次运行测试类,你会发现不一样的结果:
I am a student
至此,我们已经初步了解了lookup-method子元素所提供的大致功能,相信这时再次去看它的属性提取源码会觉得更有针对性。
/** * Parse lookup-override sub-elements of the given bean element. */ public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); // 仅当在bean下面有子元素下且为lookup-method时有效 if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) { Element ele = (Element) node; // 获取要修饰的方法 String methodName = ele.getAttribute(NAME_ATTRIBUTE); // 获取配置返回的bean String beanRef = ele.getAttribute(BEAN_ELEMENT); LookupOverride override = new LookupOverride(methodName, beanRef); override.setSource(extractSource(ele)); overrides.addOverride(override); } } }
上面的代码很熟悉,似乎与parseMetaElements的代码大同小异,最大的区别就是在if判断中的节点名称在这里被修改为LOOKUP_METHOD_ELEMENT。还有,在数据存储上面通过使用LookupOverride类型的实体类来进行数据承载并记录在AbstractBeanDefinition中的methodOverrides属性中。
5. 解析子元素replaced-method
这个方法主要是对bean中replaced-method子元素的提取,在开始提取分析之前我们还是预先介绍下这个元素的用法。
方法替换:可以在运行时用新的方法替换现有的方法。与之前的lookup-method不同的是,replaced-method不但可以动态地替换返回实体bean,而且还能动态地更改原有方法的逻辑。我们来看看使用示例。
(1)在changeMe中完成某个业务逻辑:
public class UserChange { public void changeMe() { System.out.println("change me"); } }
(2)在运营一段时间后需要改变原有的业务逻辑:
public class UserChangeReplacer implements MethodReplacer { @Override public Object reimplement(Object obj, Method method, Object[] args) throws Throwable { System.out.println("我替换了原有的方法"); return null; } }
(3)使替换后的类生效:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userChange" class="org.cellphone.uc.UserChange"> <replaced-method name="changeMe" replacer="replacer"/> </bean> <bean id="replacer" class="org.cellphone.uc.UserChangeReplacer"/> </beans>
(4)测试:
public class ReplaceMain { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring/replace-test.xml"); UserChange change = (UserChange) context.getBean("userChange"); change.changeMe(); } }
好了,运行测试类就可以看到预期的效果了,控制台成功打印出“我替换了原有的方法”,也就是说我们做到了动态替换原有方法,知道了这个元素的用法,我们再次来看元素的提取过程:
/** * Parse replaced-method sub-elements of the given bean element. */ public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) { NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); // 仅当在bean下面有子元素下且为replaced-method时有效 if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) { Element replacedMethodEle = (Element) node; // 提取要替换的旧的方法 String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE); // 提取对应的新的替换方法 String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE); ReplaceOverride replaceOverride = new ReplaceOverride(name, callback); // Look for arg-type match elements. List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT); for (Element argTypeEle : argTypeEles) { // 记录参数 String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE); match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle)); if (StringUtils.hasText(match)) { replaceOverride.addTypeIdentifier(match); } } replaceOverride.setSource(extractSource(replacedMethodEle)); overrides.addOverride(replaceOverride); } } }
我们可以看到无论是lookup-method还是replaced-method都是构造了一个MethodOverride,并最终记录在了AbstractBeanDefinition中的methodOverrides属性中。而这个属性如何使用以完成它所提供的功能我们会在后续的章节进行详细介绍。
6. 解析子元素constructor-arg
对构造函数的解析式非常常用,也是非常复杂的,下面举个简单的小例子:
<beans> <!-- 默认情况是按照参数的顺序注入,当指定index索引后就可以改变注入参数的顺序 --> <bean id= "helloBean" class="com.HelloBean"> <constructor-arg index="0"> <value>Hello</value> </cibstructor-arg> <constructor-arg index="1"> <value>World</value> </cibstructor-arg> </bean> </beans>
上面的配置是Spring构造函数配置中最基础的配置,实现的功能就是对HelloBean自动寻找对应的构造函数,并在初始化的时候将设置的参数传入。那么让我们来看看具体的XML解析过程。
对于constructor-arg子元素的解析,Spring是通过parseConstructorArgElements函数来实现的,具体的代码如下:
/** * Parse constructor-arg sub-elements of the given bean element. */ public void parseConstructorArgElements(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, CONSTRUCTOR_ARG_ELEMENT)) { // 解析constructor-arg parseConstructorArgElement((Element) node, bd); } } }
这个结构中遍历所有子元素,也就是提取所有constructor-arg,然后进行解析,具体的解析被放置在了另个函数parseConstructorArgElement中,
/** * Parse a constructor-arg element. */ public void parseConstructorArgElement(Element ele, BeanDefinition bd) { // 提取index属性 String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE); // 提取type属性 String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE); // 提取name属性 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); if (StringUtils.hasLength(indexAttr)) { try { int index = Integer.parseInt(indexAttr); if (index < 0) { error("'index' cannot be lower than 0", ele); } else { try { this.parseState.push(new ConstructorArgumentEntry(index)); // 解析ele对应的属性元素 Object value = parsePropertyValue(ele, bd, null); ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); if (StringUtils.hasLength(typeAttr)) { valueHolder.setType(typeAttr); } if (StringUtils.hasLength(nameAttr)) { valueHolder.setName(nameAttr); } valueHolder.setSource(extractSource(ele)); // 不允许重复指定相同参数 if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) { error("Ambiguous constructor-arg entries for index " + index, ele); } else { bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder); } } finally { this.parseState.pop(); } } } catch (NumberFormatException ex) { error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele); } } else { // 没有index属性则自动寻找 try { this.parseState.push(new ConstructorArgumentEntry()); Object value = parsePropertyValue(ele, bd, null); ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); if (StringUtils.hasLength(typeAttr)) { valueHolder.setType(typeAttr); } if (StringUtils.hasLength(nameAttr)) { valueHolder.setName(nameAttr); } valueHolder.setSource(extractSource(ele)); bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder); } finally { this.parseState.pop(); } } }
上面一段代码涉及的逻辑并不复杂,首先是提取constructor-arg上必要的属性(index、type、name)。
如果配置中指定了index属性,那么操作步骤如下:
(1)解析constructor-arg的子元素。
(2)使用ConstructorArgumentValues.ValueHolder来封装解析出来的元素。
(3)将type、name和index属性一并封装在ConstructorArgumentValues.ValueHolder中并添加至当前BeanDefinition的constructorArgumentValues的indexedArgumentValues属性中。
如果没有指定index属性,那么操作步骤如下:
(1)解析constructor-arg的子元素。
(2)使用ConstructorArgumentValues.ValueHolder来封装解析出来的元素。
(3)将type、name和index属性一并封装在ConstructorArgumentValues.ValueHolder中并添加至当前BeanDefinition的constructorArgumentValues的genericArgumentValues属性中。
可以看到,对于是否定义index属性,Spring的处理流程是不同的,关键在于属性信息被保存的位置。
了解整个流程后,继续尝试了解解析构造函数配置中子元素的过程,进入parsePropertyValue:
/** * Get the value of a property element. May be a list etc. * Also used for constructor arguments, "propertyName" being null in this case. */ @Nullable public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) { String elementName = (propertyName != null ? "<property> element for property '" + propertyName + "'" : "<constructor-arg> element"); // Should only have one child element: ref, value, list, etc. // 一个属性只能对应一种类型:ref、value、list等 NodeList nl = ele.getChildNodes(); Element subElement = null; for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); // 对应description或者meta不处理 if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) && !nodeNameEquals(node, META_ELEMENT)) { // Child element is what we're looking for. if (subElement != null) { error(elementName + " must not contain more than one sub-element", ele); } else { subElement = (Element) node; } } } // 解析constructor-arg上的ref属性 boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); // 解析constructor-arg上的value属性 boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); if ((hasRefAttribute && hasValueAttribute) || ((hasRefAttribute || hasValueAttribute) && subElement != null)) { /** * 在constructor-arg上不存在: * 1. 同时既有ref属性又有value属性 * 2. 存在ref属性或者value属性且又有子元素 */ error(elementName + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele); } if (hasRefAttribute) { // ref属性的处理,使用RuntimeBeanReference封装对应的ref名称 String refName = ele.getAttribute(REF_ATTRIBUTE); if (!StringUtils.hasText(refName)) { error(elementName + " contains empty 'ref' attribute", ele); } RuntimeBeanReference ref = new RuntimeBeanReference(refName); ref.setSource(extractSource(ele)); return ref; } else if (hasValueAttribute) { // value属性的处理,使用TypedStringValue封装 TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE)); valueHolder.setSource(extractSource(ele)); return valueHolder; } else if (subElement != null) { // 解析子元素 return parsePropertySubElement(subElement, bd); } else { // Neither child element nor "ref" or "value" attribute found. // 既没有ref也没有value也没有子元素,Spring蒙圈了 error(elementName + " must specify a ref or value", ele); return null; } }
从代码上看,对构造函数中属性元素的解析经历了一下几个过程。
(1)略过descrition或者meta。
(2)提取constructor-arg上的ref和value属性,以便于根据规则验证正确性,其规则为在constructor-arg上不存在以下情况。
-
同时既有ref属性又有value属性。
-
存在ref属性或者value属性且又有子元素。
(3)ref属性的处理。使用RuntimeBeanReference封装对应的ref名称,如:
<constructor-arg ref = "hello" />
(4)value属性的处理。使用TypedStringValue封装,如:
<constructor-arg value = "hello" />
(5)子元素的处理。如:
<constructor-arg> <map> <entry key = "key" value = "value" /> </map> </constructor-arg>
而对于子元素的处理,例如这里提到的在构造函数中又嵌入了子元素map是怎么实现的呢?parsePropertySubElement中实现了对各种子元素的分类处理。
@Nullable public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd) { return parsePropertySubElement(ele, bd, null); } /** * Parse a value, ref or collection sub-element of a property or * constructor-arg element. * @param ele subelement of property element; we don't know which yet * @param defaultValueType the default type (class name) for any * {@code <value>} tag that might be created */ @Nullable public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) { if (!isDefaultNamespace(ele)) { return parseNestedCustomElement(ele, bd); } else if (nodeNameEquals(ele, BEAN_ELEMENT)) { BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd); if (nestedBd != null) { nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd); } return nestedBd; } else if (nodeNameEquals(ele, REF_ELEMENT)) { // A generic reference to any name of any bean. String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); boolean toParent = false; if (!StringUtils.hasLength(refName)) { // A reference to the id of another bean in a parent context. // 解析parent refName = ele.getAttribute(PARENT_REF_ATTRIBUTE); toParent = true; if (!StringUtils.hasLength(refName)) { error("'bean' or 'parent' is required for <ref> element", ele); return null; } } if (!StringUtils.hasText(refName)) { error("<ref> element contains empty target attribute", ele); return null; } RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent); ref.setSource(extractSource(ele)); return ref; } // 对idref元素的解析 else if (nodeNameEquals(ele, IDREF_ELEMENT)) { return parseIdRefElement(ele); } // 对value子元素的解析 else if (nodeNameEquals(ele, VALUE_ELEMENT)) { return parseValueElement(ele, defaultValueType); } // 对null子元素的解析 else if (nodeNameEquals(ele, NULL_ELEMENT)) { // It's a distinguished null value. Let's wrap it in a TypedStringValue // object in order to preserve the source location. TypedStringValue nullHolder = new TypedStringValue(null); nullHolder.setSource(extractSource(ele)); return nullHolder; } else if (nodeNameEquals(ele, ARRAY_ELEMENT)) { // 解析array子元素 return parseArrayElement(ele, bd); } else if (nodeNameEquals(ele, LIST_ELEMENT)) { // 解析list子元素 return parseListElement(ele, bd); } else if (nodeNameEquals(ele, SET_ELEMENT)) { // 解析set子元素 return parseSetElement(ele, bd); } else if (nodeNameEquals(ele, MAP_ELEMENT)) { // 解析map子元素 return parseMapElement(ele, bd); } else if (nodeNameEquals(ele, PROPS_ELEMENT)) { // 解析props子元素 return parsePropsElement(ele); } else { error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele); return null; } }
可以看到,在上面的函数中实现了所有可支持的子类的分类处理。
7. 解析子元素property
parsePropertyElements函数完成了对property属性的提取,property使用方式如下:
<bean id = "test" class = "test.TestClass"> <property name = "testStr" value = "testStr" /> </bean>
具体的解析过程如下:
/** * 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); } } }
有了之前分析构造函数的经验,这个函数并不难理解,无非是提取所有property的子元素,然后调用parsePropertyElement处理,parsePropertyElement代码如下:
/** * Parse a property element. */ public void parsePropertyElement(Element ele, BeanDefinition bd) { String propertyName = ele.getAttribute(NAME_ATTRIBUTE); if (!StringUtils.hasLength(propertyName)) { error("Tag 'property' must have a 'name' attribute", ele); return; } this.parseState.push(new PropertyEntry(propertyName)); try { // 不允许多次对同一属性配置 if (bd.getPropertyValues().contains(propertyName)) { error("Multiple 'property' definitions for property '" + propertyName + "'", ele); return; } Object val = parsePropertyValue(ele, bd, propertyName); PropertyValue pv = new PropertyValue(propertyName, val); parseMetaElements(ele, pv); pv.setSource(extractSource(ele)); bd.getPropertyValues().addPropertyValue(pv); } finally { this.parseState.pop(); } }
可以看到上面函数与构造函数注入方式不同的是将返回值使用PropertyValue进行封装,并记录在BeanDefinition中的propertyValues属性中。
8. 解析子元素qualifier
对于qualifier元素的获取,我们接触更多的是注解的方式,在使用Spring框架中进行自动注入时,Spring容器中匹配的候选Bean数目必须有且只有一个。当找不到一个匹配的Bean时,Spring容器将抛出BeanCreationException异常,并指出必须至少拥有一个匹配的Bean。
Spring允许我们通过Qualifier指定注入Bean的名称,这样歧义就消除了,而对于配置方式使用如下:
<bean id="userChange" class="org.cellphone.uc.UserChange"> <qualifier type="org.springframework.beans.factory.annotation.Qualifier" value="uc"/> </bean>
其解析过程与之前大同小异,这里不再重复叙述。