第5章bean 标签解析
在第4章中对BeanDefinition的注册流程进行了相关分析,在SpringXML开发模式下BeanDefinition的数据来源是SpringXML原始标签中的bean标签,该标签会定义BeanDefinition对象中的各个属性。本章将围绕bean标签进行测试环境搭建和bean标签解析流程的分析。在Spring 中对于SpringXML文件中bean标签的解析过程如下。
(1) 将 Element 交给 BeanDefinitionParserDelegate 解析。
(2) 注册 BeanDefinition。
(3) 发布组件注册事件。
5.1创建 bean 标签解析环境
在进行bean标签的源码分析之前,还需要做一些准备工作: 搭建一个bean标签解析的环境。
5.1.1编写 SpringXML 配置文件
下面先创建一个 SpringXML 配置文件,并将其命名为beannode.xml,向该beannode.xml文件中填充下面这段代码。
5.1.2编写beannode对应的测试用例
创建一个名为 BeanNodeTest的Java 对象,代码如下。
class BeanNodeTest {
@Test
void testBean() {
ClassPathXmlApplicationContext context
= new ClassPathXmlApplicationContext("META-INF/bean-node.xml");
Object people = context.getBean("people");
context.close();
}
}
通过上述操作测试用例准备完毕,下面请回忆一下Spring中对于bean标签的解析方法。Spring中对于bean标签解析的方法签名是org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#processBeanDefinition。processBeanDefinition方法的代码如下。
protected void processBeanDefinition(Element ele,BeanDefinitionParserDelegate delegate) {
//创建BeanDefinition
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//BeanDefinition 装饰
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele,bdHolder);
try {
//注册BeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().
getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'",ele,ex);
}
//component注册事件触发
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
Spring中通过BeanDefinitionParserDelegate#parseBeanDefinitionElement方法来对 Element对象进行解析,映射到XML文件中Element对象就是一个XML标签,下面将进入到该方法的分析阶段。
5.2parseBeanDefinitionElement方法处理
5.2.1parseBeanDefinitionElement 第一部分处理
通过前文找到了需要分析的方法,Spring中对于bean标签的id属性和name属性的处理代码如下。
String id = ele.getAttribute(ID_ATTRIBUTE);
//获取 name
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
//别名列表
List aliases = new ArrayList<>();
//是否有 name 属性
if (StringUtils.hasLength(nameAttr)) {
//获取名称列表,根据 ,; 进行分隔
String[] nameArr =
StringUtils.tokenizeToStringArray(nameAttr,MULTI_VALUE_ATTRIBUTE_DELIMITERS);
//添加所有
aliases.addAll(Arrays.asList(nameArr));
}
第一部分的处理很简单,获取bean标签中的id和name属性,对name属性做分隔符切分后将切分结果作为别名。在第一部分中提到了分隔符这一信息,在这段代码中的分隔符总共有以下3种。
(1) 逗号。
(2) 分号。
(3) 空格。
5.2.2parseBeanDefinitionElement 第二部分处理
接下来将进入第二部分的分析,Spring中对于BeanName的处理代码如下。
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
//别名的第一个设置为beanName
beanName = aliases.remove(0);
if (logger.isTraceEnabled()) {
logger.trace("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
//BeanDefinition 为空
if (containingBean == null) {
//判断 beanName 是否被使用,Bean 别名是否被使用
checkNameUniqueness(beanName,aliases,ele);
}
在第二部分的处理中可以再将其分为以下两小段内容。
(1) 关于 BeanName 的推论。
(2) 关于 BeanName 是否被使用,别名是否被使用的验证。
Spring中对于BeanName 的推论规则如下。
(1) BeanName 可以是 bean 标签中的id属性。
(2) BeanName 可以是 bean 标签中的name属性,注意name属性可以存在多个,多个name使用分隔符分隔,当存在多个name属性时会取第一个值作为BeanName。
(3) 当bean标签中同时出现id属性和name属性时会用id作为BeanName。
在了解了BeanName的生成规则后来编写相关测试用例,首先要变写的是关于id的测试用例。
根据前文的分析此时可以推论出BeanName 应该是people,调试信息如图5.1所示。
接下来编写关于name的测试用例。
根据前文的分析此时可以推论出BeanName 应该是peopleBean,调试信息如图5.2所示。
图5.1设置id时的BeanName
图5.2设置name时的BeanName
最后来编写id和name同时设置的测试用例。
根据前文的分析此时可以推论出BeanName 应该是p1,调试信息如图5.3所示。
图5.3同时设置id和name时的BeanName
关于 BeanName 的推论相关的分析到此结束,下面进入到第二部分关于BeanName是否被使用,别名是否被使用的验证的分析。Spring中checkNameUniqueness的完整代码内容如下。
protected void checkNameUniqueness(String beanName,List aliases,Element beanElement) {
//当前寻找的name
String foundName = null;
//是否有BeanName
//使用过的name中是否存在
if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) {
foundName = beanName;
}
if (foundName == null) {
//寻找匹配的第一个
foundName = CollectionUtils.findFirstMatch(this.usedNames,aliases);
}
//抛出异常
if (foundName != null) {
error("Bean name '" + foundName + "' is already used in this element",beanElement);
}
//加入使用队列
this.usedNames.add(beanName);
this.usedNames.addAll(aliases);
}
在checkNameUniqueness方法中需要重点关注的是usedNames变量,usedNames变量是指已经使用过的名称包含 BeanName 和 Alias。整个验证过程就是判断传递的参数是否在 usedNames 容器中存在,一旦存在就会抛出异常。如果不存在则将 BeanName 和 Alias 加入 usedNames容器等待后续使用。
至此,parseBeanDefinitionElement第二部分的分析告一段落,下面将进入第三部分的分析。
5.2.3parseBeanDefinitionElement 第三部分处理
Spring中parseBeanDefinitionElement方法中的第三部分代码如下。
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele,beanName,containingBean);
这段代码其实是一个方法的调用,真正应该阅读的代码应该是下面这一段。
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele,String beanName,@Nullable BeanDefinition containingBean) {
//第一部分
//设置阶段Bean定义解析阶段
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);
}
//第二部分
//创建BeanDefinition
AbstractBeanDefinition bd = createBeanDefinition(className,parent);
//BeanDefinition 属性设置
parseBeanDefinitionAttributes(ele,beanName,containingBean,bd);
//设置描述
bd.setDescription(DomUtils.getChildElementValueByTagName(ele,DESCRIPTION_ELEMENT));
//元信息设置 meta 标签解析
parseMetaElements(ele,bd);
//lookup-override 标签解析
parseLookupOverrideSubElements(ele,bd.getMethodOverrides());
//replaced-method 标签解析
parseReplacedMethodSubElements(ele,bd.getMethodOverrides());
//constructor-arg 标签解析
parseConstructorArgElements(ele,bd);
//property 标签解析
parsePropertyElements(ele,bd);
//qualifier 标签解析
parseQualifierElements(ele,bd);
//资源设置
bd.setResource(this.readerContext.getResource());
//source 设置
bd.setSource(extractSource(ele));
return bd;
}
在这个方法中Spring做了11步操作,下面将围绕这11步依次解析它们的处理行为。操作细节如下。
(1) 处理className和parent属性。
(2) 创建基本的 BeanDefinition对象,具体类: AbstractBeanDefinition、GenericBeanDefinition。
(3) 读取 bean 标签的属性,为 BeanDefinition 对象进行赋值。
(4) 处理描述标签description。
(5) 处理 meta标签。
(6) 处理lookupoverride标签。
(7) 处理replacedmethod标签。
(8) 处理constructorarg标签。
(9) 处理property标签。
(10) 处理qualifier标签。
(11) 设置资源对象和source属性。
注意: 在这段方法中出现的parseState对象操作不属于本节的分析内容,可以简单被理解成这是一个阶段标记。
1. 处理class name和parent属性
Spring中对于bean标签的 class name 和 parent 两个属性的处理代码如下。
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);
}
在这段代码中对于className 和parent的处理十分简单,直接从Element对象中提取,提取完成数据后为下面创建AbstractBeanDefinition对象提供了基础数据支持。下面来看创建AbstractBeanDefinition对象的细节。
2. 创建AbstractBeanDefinition对象
Spring中对于创建AbstractBeanDefinition对象的代码如下。
//parseBeanDefinitionElement 方法内调用
AbstractBeanDefinition bd = createBeanDefinition(className,parent);
//createBeanDefinition 详情
protected AbstractBeanDefinition
createBeanDefinition(@Nullable String className,@Nullable String parentName)
throws ClassNotFoundException {
return BeanDefinitionReaderUtils.createBeanDefinition(
parentName,className,this.readerContext.getBeanClassLoader());
}
在这段方法中存在一层引用方法,这段引用方法的代码如下。
public static AbstractBeanDefinition createBeanDefinition(
@Nullable String parentName,@Nullable String className,@Nullable ClassLoader classLoader) throws ClassNotFoundException {
GenericBeanDefinition bd = new GenericBeanDefinition();
//设置父BeanName
bd.setParentName(parentName);
if (className != null) {
if (classLoader != null) {
//设置 class
//内部是通过反射创建 class
bd.setBeanClass(ClassUtils.forName(className,classLoader));
}
else {
//设置 className
bd.setBeanClassName(className);
}
}
return bd;
}
在这段方法中,createBeanDefinition 方法是一个静态方法,其完整的方法签名是org.springframework.beans.factory.support.BeanDefinitionReaderUtils#createBeanDefinition,在这个方法中Spring对其做出了如下操作。
(1) 创建类型为GenericBeanDefinition的 BeanDefinition 对象。
(2) 为创建的GenericBeanDefinition对象进行数据设置,设置的属性是parentName和beanClass。
注意: beanClass的数据类型存在以下两种情况。
(1) beanClass的类型是 String。
(2) beanClass的类型是 Class。
下面进入调试阶段,首先编写一段SpringXML配置:
在完成配置文件编写后启动项目观察createBeanDefinition执行后的结果对象bd(BeanDefinition: bean定义信息),如图5.4所示。
图5.4createBeanDefinition执行后结果
可以看到,beanClass还是字符串类型。现在AbstractBeanDefinition基础对象已经准备完毕,接下来就是补充这个对象的其他属性。
3. 设置 BeanDefinition 的基本信息
接下来进行parseBeanDefinitionAttributes方法的解析,Spring中parseBeanDefinitionAttribute的细节代码如下。
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele,
String beanName,
@Nullable BeanDefinition containingBean,AbstractBeanDefinition bd) {
//是否存在 singleton 属性
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));
}
//Bean 定义是否为空
else if (containingBean != null) {
//设置 BeanDefinition 中的 scope
bd.setScope(containingBean.getScope());
}
//是否存在 abstract 属性
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
//设置 abstract 属性
bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}
//获取 lazy-init 属性
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
//是否是默认的 lazy-init 属性
if (isDefaultValue(lazyInit)) {
//获取默认值
lazyInit = this.defaults.getLazyInit();
}
//设置 lazy-init 属性
bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
//获取注入方式
//autowire 属性
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
//设置注入方式
bd.setAutowireMode(getAutowireMode(autowire));
//依赖的Bean
//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 (isDefaultValue(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);
}
//没有 init-method 的情况处理
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);
}
//没有 destroy-method 的情况处理
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;
}
这段代码的处理过程与className 和parent的处理过程基本类似,它们的处理思路都是从bean标签中提取属性对应的属性值,将属性值设置给BeanDefinition 对象(BeanDefinition对象是通过上一步创建出来),在这个处理过程中有一个值得关注的变量defaults,先来看defaults变量的定义代码。
private final DocumentDefaultsDefinition defaults = new DocumentDefaultsDefinition();
这段代码中defaults指定了一个具体的数据类型DocumentDefaultsDefinition,在DocumentDefaultsDefinition类型中存放了Spring对一些属性的默认值,DocumentDefaultsDefinition中的定义的属性及属性默认值如表5.1所示。
表5.1DocumentDefaultsDefinition属性及属性默认值
默认值属性名称默认值
lazyInit
false
merge
false
autowire
no
autowireCandidates
null
initMethod
null
destroyMethod
null
source
null
在了解defaults对象中存有的数据后,来进一步观察BeanDefinition 经过parseBeanDefinitionAttributes方法处理后得到的信息,如图5.5所示。
图5.5parseBeanDefinitionAttributes方法执行后结果
4. 设置BeanDefinition 描述信息
接下来进行BeanDefintion 描述信息设置方法的解析,首先需要编写一段SpringXML配置,在这段配置中需要设置bean标签的属性值,具体代码如下。
this is a bean description
下面是设置bean描述信息的代码内容。
bd.setDescription(DomUtils.getChildElementValueByTagName(ele,DESCRIPTION_ELEMENT));
这段代码的处理比较明了,提取当前节点下description节点中的数据,将这个数据赋值给BeanDefinition中的description属性,处理结果如图5.6所示。
图5.6设置bean描述信息后结果
5. 设置meta属性
接下来进行parseBeanDefinitionElement方法的解析。Spring中parseBeanDefinitionElement方法的详细信息如下。
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;
//获取 key 属性
String key = metaElement.getAttribute(KEY_ATTRIBUTE);
//获取 value 属性
String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
//元数据对象设置
BeanMetadataAttribute attribute = new BeanMetadataAttribute(key,value);
//设置 source
attribute.setSource(extractSource(metaElement));
//信息添加
attributeAccessor.addMetadataAttribute(attribute);
}
}
}
下面先来编写一段测试用例再进行源代码分析,这段代码处理的是meta标签的数据,改进SpringXML配置文件。
this is a bean description
通过这段配置文件可以知道,meta标签中存在两个属性 key 和value。在parseMetaElements方法中对bean标签的下级标签meta的处理就是提取key和value属性的属性值,在提取后创建BeanMetadataAttribute对象,创建对象后将其放到BeanMetadataAttributeAccessor集合中。在这段处理过程中出现了一个新的对象 BeanMetadataAttributeAccessor,这个对象是什么呢?回答这个问题需要从AbstractBeanDefinition出发,观察AbstractBeanDefinition的类图,如图5.7 所示。
图5.7AbstractBeanDefinition类图
在类图中可以直观地看到,AbstractBeanDefinition 是 BeanMetadataAttributeAccessor 的子类。对于 BeanMetadataAttributeAccessor 的设置其实就是为 AbstractBeanDefinition 添加属性。了解了这些理论知识后,经过parseMetaElements方法处理后BeanDefinition 的对象信息如图5.8 所示。
图5.8parseMetaElements执行后BeanDefinition的信息
6. lookupoverride 标签处理
在处理完meta标签后将处理lookupoverride标签。前文所编写的一些测试代码在这个方法中是不足以支持断点调试的,需要对测试代码进行补充。下面编写测试用例,假设现在有一个商店在出售水果,水果可以是苹果、香蕉等,此时客户需要通过不同的商店获取各个商店所售卖的内容,下面先定义几个Java对象。
水果对象详细代码如下。
public class Fruits {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
苹果对象详细代码如下。
public class Apple extends Fruits {
public Apple() {
this.setName("apple");
}
public void hello() {
System.out.println("hello");
}
}
商店对象详细代码如下。
public abstract class Shop {
public abstract Fruits getFruits();
}
编写SpringXML配置文件,文件名称为springlookupmethod.xml,详细代码如下。
编写测试用例,详细代码如下。
@Test
void testLookupMethodBean() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("META-INF/spring-lookup-method.xml");
Shop shop = context.getBean("shop",Shop.class);
System.out.println(shop.getFruits().getName());
assert context.getBean("apple").equals(shop.getFruits());
}
测试方法 testLookupMethodBean 的执行结果输出apple 并且测试通过。
先来整理测试用例中的执行流程,在SpringXML配置文件中配置bean标签的lookupmethod属性,在调用方法时会根据lookupmethod中的bean属性在Spring容器中寻找bean属性值对应的Bean实例,当调用lookupmethod中name属性值的方法时将bean属性对应的Bean实例作为返回结果返回。在测试用例中调用shop#getFruits方法时会在容器中找到apple这个Bean实例将其作为返回值。
简单了解使用逻辑后,现在来看Spring是如何处理lookupmethod标签的。先阅读处理方法parseLookupOverrideSubElements。
public void parseLookupOverrideSubElements(Element beanEle,MethodOverrides overrides) {
//获取子标签
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//是否有 lookup-method 标签
if (isCandidateElement(node) &&
nodeNameEquals(node,LOOKUP_METHOD_ELEMENT)) {
Element ele = (Element) node;
//获取 name 属性
String methodName = ele.getAttribute(NAME_ATTRIBUTE);
//获取 bean 属性
String beanRef = ele.getAttribute(BEAN_ELEMENT);
//创建覆盖依赖
LookupOverride override = new LookupOverride(methodName,beanRef);
//设置source
override.setSource(extractSource(ele));
overrides.addOverride(override);
}
}
}
这段处理模式和 meta 标签的处理模式基本相同。处理方式: 获取所有子节点,如果子节点是 lookupmethod就提取 name和bean两个属性,在属性获取后创建对应的 Java 对象LookupOverride 并设置给 MethodOverrides,MethodOverrides 是 BeanDefinition 的一个成员变量。了解理论内容后再看处理结果,如图5.9所示。
图5.9parseLookupOverrideSubElements执行后BeanDefinition的信息
现在对于lookupmethod标签的解析已经完成,下面思考一个问题: 在执行时发生了什么?即当调用shop.getFruits()方法时发生了什么?回答这个问题需要了解另一个知识点对象代理,本章不会对代理过程做一个完善的分析,仅做调用方法的分析。
先看shop对象,shop对象并不是一个普通的Java对象,它是一个增强对象,shop对象信息如图5.10所示。
图5.10shop对象
通过图5.10可以确认,shop对象中存在LookupOverrideMethodInterceptor对象,接下来需要找到LookupOverrideMethodInterceptor对象,在这个对象中存有需要分析的方法:
@Override
public Object intercept(Object obj,Method method,Object[] args,MethodProxy mp) throws Throwable {
LookupOverride lo =
(LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
Assert.state(lo != null,"LookupOverride not found");
Object[] argsToUse = (args.length > 0 ? args : null);
if (StringUtils.hasText(lo.getBeanName())) {
return (argsToUse != null ? this.owner.getBean(lo.getBeanName(),argsToUse) :
this.owner.getBean(lo.getBeanName()));
}
else {
return (argsToUse != null ?
this.owner.getBean(method.getReturnType(),argsToUse) :
this.owner.getBean(method.getReturnType()));
}
}
这段方法的本质就是动态代理的方法增强,可以简单理解为原有方法的执行结果被intercept替换了,替换过程如下。
(1) 通过参数method在lookupOverride容器中找到替换的LookupOverride对象。
(2) 从LookupOverride中提取beanName 属性,在Spring IoC容器(BeanFactory)中通过beanName或者 beanName+构造参数列表获得Bean实例,将Bean实例作为返回结果。
了解了执行流程后下面对一些关键信息进行复盘。在这个测试用例中不存在构造参数列表数据,代码会执行this.owner.getBean(lo.getBeanName())方法,注意owner就是BeanFactory,提取实例需要依赖它。下面来看通过method找到LookupOverride对象中LookupOverride存储的数据内容,如图5.11所示。
图5.11LookupOverride存储的数据内容
在图5.11中可以看到,需要加载的beanName是apple,现在具备获取Bean实例的工具owner,接下来就是从owner中将beanName为apple的Bean实例提取作为返回值返回即可。
7. replacedmethod 标签处理
前文对 lookupoverride做了充分的分析,在这个基础上阅读 replacedmethod方法将事半功倍,它们的底层处理方式可以说是大同小异。首先来编写replacedmethod的测试用例。
第一步需要编写MethodReplacer接口实现类,实现类代码如下。
public class MethodReplacerApple implements MethodReplacer {
@Override
public Object reimplement(Object obj,Method method,Object[] args) throws Throwable {
System.out.println("方法替换");
return obj;
}
}
编写SpringXML配置文件,文件名称为springreplacedmethod.xml,文件内容如下。
String
编写测试方法,具体代码如下。
@Test
void testReplacedMethod(){
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("META-INF/spring-replaced-method.xml");
Apple apple = context.getBean("apple",Apple.class);
apple.hello();
}
测试方法执行结果: 控制台输出方法替换。
接下来进行replacedmethod标签解析的讲解,先阅读处理代码。
public void parseReplacedMethodSubElements(Element beanEle,
MethodOverrides overrides) {
//子节点获取
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//是否包含 replaced-method 标签
if (isCandidateElement(node)
&& nodeNameEquals(node,REPLACED_METHOD_ELEMENT)) {
Element replacedMethodEle = (Element) node;
//获取 name 属性
String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
//获取 replacer
String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
//对象组装
ReplaceOverride replaceOverride = new ReplaceOverride(name,callback);
//Look for arg-type match elements.
//子节点属性
//处理 arg-type 标签
List argTypeEles
= DomUtils.getChildElementsByTagName(replacedMethodEle,ARG_TYPE_ELEMENT);
for (Element argTypeEle : argTypeEles) {
//获取 match 数据值
String match =
argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
//match 信息设置
match = (StringUtils.hasText(match) ? match :
DomUtils.getTextValue(argTypeEle));
if (StringUtils.hasText(match)) {
//添加类型标识
replaceOverride.addTypeIdentifier(match);
}
}
//设置 source
replaceOverride.setSource(extractSource(replacedMethodEle));
//重载列表添加
overrides.addOverride(replaceOverride);
}
}
}
在这一段代码中对于replacedmethod标签的处理分为以下三个步骤。
(1) 提取replacedmethod标签中的name和replacer属性。
(2) 提取子标签argtype的属性。
(3) 将提取得到的数据进行组装并赋值给BeanDefinition对象。
在了解执行流程后再看看parseReplacedMethodSubElements方法执行后BeanDefinition的数据信息,如图5.12所示。
图5.12parseReplacedMethodSubElements执行后的BeanDefinition 信息
下面来看apple.hello方法执行时发生了什么?首先明确一点,apple对象是一个代理对象,并不是一个原始的Java对象,数据信息如图5.13 所示。
图5.13replacedmethod中的apple对象
从图5.13中可以看到一个叫作 ReplaceOverrideMethodInterceptor的类名,这个类就是真正需要分析的目标,在 ReplaceOverrideMethodInterceptor对象中关于intercept方法的处理池代码如下。
@Override
public Object intercept(Object obj,Method method,Object[] args,MethodProxy mp) throws Throwable {
ReplaceOverride ro =
(ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
Assert.state(ro != null,"ReplaceOverride not found");
MethodReplacer mr = this.owner.getBean(
ro.getMethodReplacerBeanName(),MethodReplacer.class);
return mr.reimplement(obj,method,args);
}
在这个方法中 Spring 会根据 method 进行 ReplaceOverride 的搜索,在这里ReplaceOverride就是通过parseReplacedMethodSubElements方法解析得到的对象,ro数据信息如图5.14所示。
图5.14ro 数据信息
在图5.14中可以看到,在SpringXML配置中配置过的一些数据信息,Spring会通过methodReplacerBeanName+MethodReplace.class 在Spring IoC容器中找到最终的实现类并调用其方法,将方法的处理结果作为返回值返回,从而达到方法替换的作用。
8. constructorarg 标签处理
接下来将进入constructorarg 标签的源码分析阶段,进入方法分析之前需要制作一些测试用例。
首先创建一个Java对象。
public class PeopleBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public PeopleBean() {
}
public PeopleBean(String name) {
this.name = name;
}
}
在这个Java对象中定义了一个无参构造和有参构造,下面的分析将围绕有参构造进行。
在完成Java对象创建后需要编写 SpringXML配置文件,文件名称为springconstructorarg.xml,具体代码如下。
最后来编写测试方法。
@Test
void testConstructArg() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("META-INF/spring-constructor-arg.xml");
PeopleBean people = context.getBean("people",PeopleBean.class);
assert people.getName().equals("zhangsan");
}
测试用例准备完成接下来进入源代码的分析。首先找到需要分析的方法签名org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseConstructorArgElements,找到方法签名后进入方法内部阅读内部的代码。
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);
}
}
}
这段方法中进行了另一个方法parseConstructorArgElement的引用,下面请阅读在parseConstructorArgElement中的第一部分代码。
//获取 index 属性
String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
//获取 type 属性
String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
//获取 name 属性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
在第一部分代码中的处理逻辑很好理解,其主要目的是获取constructorarg标签中的index、type和name三个属性,当属性获取完成Spring会根据index属性是否存在做出两种不同的处理,在前文提到的测试用例中index属性是存在的情况,下面先进行index属性存在的分析。先阅读处理代码。
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));
//解析 property 标签
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);
}
在这段代码中需要重点关注的对象是ConstructorArgumentValues,注意在这段方法的分析中对于标签constructorarg的解析会需要进行property标签的解析,这一标签的分析会在5.4.9节中进行,本节对于property标签处理暂时跳过。
通过阅读源代码可以确认在标签中提取的index、value和type三个属性最终放到了ConstructorArgumentValues.ValueHolder对象和ConstructorArgumentValues中,进入调试阶段观察具体的数据存储,首先观察type 和 value的存储,信息如图5.15所示。
图5.15type 和 value的存储信息
进一步观察index、type和value的存储,信息如图5.16所示。
图5.16index、type和value的存储信息
现在对于index数据存在的情况相关源码分析结束,下面进入index不存在的情况,来看Spring是如何对这种情况进行处理的。首先需要编写一个index不填写的测试用例,然后再编写一个SpringXML配置文件,文件为springconstructorarg.xml,具体代码如下。
编写完成SpringXML配置文件后来编写测试方法。
@Test
void testConstructArgForName() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("META-INF/spring-constructor-arg.xml");
PeopleBean people = context.getBean("people2",PeopleBean.class);
assert people.getName().equals("zhangsan");
}
在Spring中这种模式称为参数名称绑定数据信息,下面进入这种模式的源代码分析,首先请阅读下面这段代码。
try {
//设置阶段,构造函数处理阶段
this.parseState.push(new ConstructorArgumentEntry());
//解析 property 标签
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();
}
将这段处理形式和index存在情况下的模式进行对比,两种模式的差异是index的控制,其他信息处理相同。在这个方法中,关键对象是ConstructorArgumentValues.ValueHolder,处理模式相同的情况下分析内容不多直接进入调试阶段来看经过处理后的valueHolder对象数据,信息如图5.17所示。
图5.17参数名称绑定模式下valueHolder对象信息
执行这段代码之后再进一步观察BeanDefinition 的对象信息,如图5.18所示。
图5.18经过构造标签解析后BeanDefinition的数据信息
现在构造器的数据信息已经准备完毕,请思考一个问题: 构造函数的信息都准备完毕如何使用?在Spring中对于这种情况的处理是交给一个方法进行处理的,方法的具体签名是org.springframework.beans.BeanUtils#instantiateClass(java.lang.reflect.Constructor,java.lang.Object…),可以根据需求对该方法进行阅读,本节仅该方法的调用链路整理出来,如图5.19所示。
图5.19instantiateClass调用堆栈
9. property 标签处理
接下来将进入property标签处理的源码分析。首先需要编写一个测试用例,第一步编写SpringXML配置文件,文件名为springproperty.xml,该文件的代码内容如下。
完成SpringXML配置文件的编写后需要进行单元测试的编写,具体单元测试代码如下。
@Test
void testProperty(){
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("META-INF/spring-property.xml");
PeopleBean people = context.getBean("people",PeopleBean.class);
assert people.getName().equals("zhangsan");
}
测试用例准备完毕,找到需要分析的方法parsePropertyElements,该方法的方法签名是org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertyElements,具体代码内容如下。
public void parsePropertyElements(Element beanEle,BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//是否存在 property 标签
if (isCandidateElement(node) && nodeNameEquals(node,PROPERTY_ELEMENT)) {
//解析单个标签
parsePropertyElement((Element) node,bd);
}
}
}
在这段方法中没有太多的处理细节,最终的能力提供者是parsePropertyElement方法,该方法是最终的分析目标,先阅读方法的内容。
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;
}
//解析 property 标签
Object val = parsePropertyValue(ele,bd,propertyName);
//构造 PropertyValue 对象
PropertyValue pv = new PropertyValue(propertyName,val);
//解析元信息
parseMetaElements(ele,pv);
pv.setSource(extractSource(ele));
//添加 pv 结构
bd.getPropertyValues().addPropertyValue(pv);
}
finally {
this.parseState.pop();
}
}
在parsePropertyElement方法中总共有以下四步处理。
(1) 提取property标签的name属性值。
(2) 提取property标签的value属性值。
(3) 解析property标签中可能存在的meta标签。
(4) 将数据解析封装后设置给 BeanDefinition。
在整个处理流程中需要关注property标签的数据存储对象PropertyValue。在上述四个处理步骤中第一步和第三步是一个比较简单的处理,比较复杂的内容是第二步操作,下面将对第二步操作进行详细分析。首先阅读springbeans.dtd文件中对于property标签的定义。
进一步阅读Spring中对property标签的另一种定义方式,该方式的数据在springbeans.xsd中存放,具体内容如下。
".
]]>
..." element.
]]>
在了解property的两种定义内容后下面对parsePropertyValue方法的处理流程进行分析,先阅读处理方法。
@Nullable
public Object parsePropertyValue(Element ele,BeanDefinition bd,@Nullable String propertyName) {
String elementName = (propertyName != null ?
" element for property '" + propertyName + "'" :
" element");
//计算子节点
//Should only have one child element: ref,value,list,etc.
NodeList nl = ele.getChildNodes();
Element subElement = null;
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element &&
!nodeNameEquals(node,DESCRIPTION_ELEMENT) &&
!nodeNameEquals(node,META_ELEMENT)) {
if (subElement != null) {
error(elementName + " must not contain more than one sub-element",ele);
}
else {
subElement = (Element) node;
}
}
}
//ref 属性是否存在
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
//value 属性是否存在
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
if ((hasRefAttribute && hasValueAttribute) ||
((hasRefAttribute || hasValueAttribute) && subElement != null)) {
error(elementName +
" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element",ele);
}
if (hasRefAttribute) {
//获取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 valueHolder =
new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
valueHolder.setSource(extractSource(ele));
return valueHolder;
}
else if (subElement != null) {
return parsePropertySubElement(subElement,bd);
}
else {
error(elementName + " must specify a ref or value",ele);
return null;
}
}
在parsePropertyValue方法中可以将其分为下面四个步骤进行阅读。
(1) 提取 property 下的子节点标签中非 description和meta标签。
(2) 提取 property 中的 ref 属性,存在的情况下创建RuntimeBeanReference对象并返回。
(3) 提取 property 中的 value 属性,存在的情况下创建TypedStringValue对象并返回。
(4) 解析第一步中得到的子标签信息。
通过前文对property标签的两个定义文件中的内容可以得出property的子标签有11个,分别如下。
(1) meta。
(2) bean。
(3) ref。
(4) idref。
(5) value。
(6) null。
(7) array。
(8) list。
(9) set。
(10) map。
(11) props。
在这11个下级标签中,除了meta标签以外都是第一步中会提取得到的数据内容。第二步中获取ref属性值操作很简单,直接使用getAttribute即可获取,创建RuntimeBeanReference对象的过程也是一个简单的new操作。第三步和第二步的操作理论上相同,都是获取标签中的数据内容再进行特定对象的创建。第四步处理子标签的行为其实就是对上述标签的处理(除meta标签外),处理这些标签时所使用的方法是parsePropertySubElement,下面将对这个方法做一个分析。
开始分析之前需要准备测试用例中需要的代码,首先需要的就是JavaBean对象,该JavaBean内容如下。
public class PeopleBean {
private String name;
private List list;
private Map map;
//省略构造函数,getter,setter
}
完成JavaBean的改造后进一步修改SpringXML配置文件的内容,配置文件名称为springproperty.xml,文件内容如下。
a
b
编写完测试代码后先对list标签进行分析,首先找到list标签的处理方法parseListElement,这里需要注意对于list标签、array标签、set标签最终都是交给parseCollectionElements方法来进行处理,在这个处理过程中就是将value标签的数据提取转换为对应的Java对象,下面来看经过parseListElement处理过后的数据内容,如图5.20所示。
图5.20parseListElement执行后的数据
完成了list标签的处理流程分析后接下来需要分析的就是map标签。在SpringXML配置文件中编写了map标签的keytype属性和valuetype属性,以及子标签entry和entry标签的key属性和value属性。在当前的测试用例中并没有采用ref属性,仅以字符串作为直接字面量作为分析目标,处理map标签的方法是parseMapElement。在处理 list 标签时采取的是字面量,最终得到的对象类型是TypedStringValue。在map标签处理中用到的也是这个类型,只不过是从 list 存储转换成 map存储。下面来看处理后的结果,如图5.21所示。
图5.21parseMapElement方法执行后处理结果
现在完成了map标签的处理内容,剩下还有一些标签的处理,读者可以对这些具体标签的处理方法再做进一步的阅读。
10. qualifier 标签处理
接下来将进入qualifier标签的解析分析。首先需要编写一个测试用例,第一步创建一个JavaBean对象,该对象名称是PeopleBeanTwo,详细代码如下。
public class PeopleBeanTwo {
@Autowired
@Qualifier("p1")
private PeopleBean peopleBean;
//省略 getter 和 setter
}
完成JavaBean编写后进一步编写SpringXML配置文件内容,该文件名称为springqualifier.xml,详细代码如下。
最后编写单元测试。
@Test
void testQualifier() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("META-INF/spring-qualifier.xml");
PeopleBeanTwo peopleTwo = context.getBean("p2",PeopleBeanTwo.class);
assert peopleTwo.getPeopleBean().equals(
context.getBean("peopleBean",PeopleBean.class));
}
现在测试用例准备完毕,接下来找到需要分析的方法parseQualifierElements,先阅读其中的代码内容。
public void parseQualifierElements(Element beanEle,AbstractBeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node,QUALIFIER_ELEMENT)) {
//单个解析
parseQualifierElement((Element) node,bd);
}
}
}
由于Spring 是允许qualifier标签在bean标签下存在多个的,在Spring中对单个qualifier标签的处理是交给parseQualifierElement方法进行,下面先阅读parseQualifierElement的方法内容。
public void parseQualifierElement(Element ele,AbstractBeanDefinition bd) {
//获取 type 属性
String typeName = ele.getAttribute(TYPE_ATTRIBUTE);
if (!StringUtils.hasLength(typeName)) {
error("Tag 'qualifier' must have a 'type' attribute",ele);
return;
}
//设置阶段,处理 qualifier 阶段
this.parseState.push(new QualifierEntry(typeName));
try {
//自动注入对象创建
AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName);
//设置源
qualifier.setSource(extractSource(ele));
//获取 value 属性
String value = ele.getAttribute(VALUE_ATTRIBUTE);
if (StringUtils.hasLength(value)) {
//设置属性
qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY,value);
}
NodeList nl = ele.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) &&
nodeNameEquals(node,QUALIFIER_ATTRIBUTE_ELEMENT)) {
Element attributeEle = (Element) node;
//获取 key 属性
String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE);
//获取 value 属性
String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE);
if (StringUtils.hasLength(attributeName) &&
StringUtils.hasLength(attributeValue)) {
//key-value 属性映射
BeanMetadataAttribute attribute =
new BeanMetadataAttribute(attributeName,attributeValue);
attribute.setSource(extractSource(attributeEle));
//添加 qualifier 属性值
qualifier.addMetadataAttribute(attribute);
}
else {
error("Qualifier 'attribute' tag must have a
'name' and 'value'",attributeEle);
return;
}
}
}
//添加 qualifier
bd.addQualifier(qualifier);
}
finally {
//移除阶段
this.parseState.pop();
}
}
这个解析过程就是对qualifier标签提取各个属性值再将其转换成Java对象,在Spring中qualifier标签对应的Java对象是AutowireCandidateQualifier,下面来看经过解析后的数据,如图5.22所示。
图5.22AutowireCandidateQualifier 数据信息
11. 设置resource和source属性
前文已经完成了bean标签的属性及下级标签的分析,最后还有两个属性需要设置,这两个属性分别是resource和source,对于这两个属性的测试使用本章中提到的任何一个测试用例都可以进行测试。既然提到了resource,那么什么是resource(资源)呢?在Java工程中有一个resources文件夹,一般情况下认为该文件夹下的内容就是资源。在Spring中对于资源的定义可以简单地理解为SpringXML配置文件,不过还有其他的resource,不只是配置文件,但是在此时需要设置的resource属性就是SpringXML配置文件,具体信息如图5.23所示。
在完成resource对象的设置后,Spring对source进行了设置。在Spring中source的解析是交给SourceExtractor类进行处理的,下面了解一下SourceExtractor的类图,如图5.24所示。
图5.23resource对象信息
图5.24SourceExtractor类图
通过类图可以发现Spring提供了以下两个关于SourceExtractor的实现。
(1) NullSourceExtractor实现类直接将null作为解析结果进行返回。
(2) PassThroughSourceExtractor实现类直接将需要解析的对象本身作为解析结果返回。
在bean标签解析的过程中,SourceExtractor的具体实现类是NullSourceExtractor,所以在设置BeanDefinition的source属性时设置的数据是null,执行结果如图5.25所示。
图5.25执行后source的数据内容
至此,对于SpringXML配置文件中关于bean标签的解析全部完成,现在Spring得到了bean标签对应的BeanDefinition 对象。
5.3BeanDefinition装饰
在得到BeanDefinition对象后,Spring对这个对象进行了装饰操作,接下来将对该处理进行分析。在DefaultBeanDefinitionDocumentReader#processBeanDefinition方法中可以找到Spring获取BeanDefinition 对象后的操作: 对象装饰(数据补充)。下面请阅读处理代码。
protected void processBeanDefinition(Element ele,BeanDefinitionParserDelegate delegate) {
//创建BeanDefinition
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//BeanDefinition 装饰
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele,bdHolder);
try {
//注册BeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().
getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'",ele,ex);
}
//component注册事件触发
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
在这段方法中真正的处理是交给decorateBeanDefinitionIfRequired方法进行的,进一步追踪源代码阅读decorateBeanDefinitionIfRequired方法内容:
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
Element ele,BeanDefinitionHolder originalDef,@Nullable BeanDefinition containingBd) {
BeanDefinitionHolder finalDefinition = originalDef;
NamedNodeMap attributes = ele.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node node = attributes.item(i);
finalDefinition = decorateIfRequired(node,finalDefinition,containingBd);
}
NodeList children = ele.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
finalDefinition = decorateIfRequired(node,finalDefinition,containingBd);
}
}
return finalDefinition;
}
在这段方法中可以整理出下面两种情况需要对BeanDefinition进行装饰。
(1) 当bean标签存在属性时。
(2) 当bean标签存在下级标签时。
在decorateBeanDefinitionIfRequired方法中确认了需要进行装饰的原因,最终进行装饰处理的方法是decorateIfRequired,该方法就是需要分析的重点了,请先阅读源码。
public BeanDefinitionHolder decorateIfRequired(
Node node,BeanDefinitionHolder originalDef,
@Nullable BeanDefinition containingBd) {
//命名空间 url
String namespaceUri = getNamespaceURI(node);
if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
NamespaceHandler handler
= this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler != null) {
//命名空间进行装饰
BeanDefinitionHolder decorated =
handler.decorate(node,originalDef,new ParserContext(this.readerContext,this,containingBd));
if (decorated != null) {
return decorated;
}
}
else if (namespaceUri.startsWith("http://www.springframework.org/schema/")) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]",node);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("No Spring NamespaceHandler found
for XML schema namespace [" + namespaceUri + "]");
}
}
}
return originalDef;
}
在这个方法的处理过程中可以发现比较熟悉的对象是NamespaceHandler,在第4章中对于NamespaceHandler接口做了关于自定义标签解析的相关内容,下面将延续第4章中的测试用例补充实现decorate方法,修改UserXsdNamespaceHandler类,具体修改后内容如下。
public class UserXsdNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("user_xsd",new UserXsdParser());
}
@Override
public BeanDefinitionHolder decorate(Node node,BeanDefinitionHolder definition,ParserContext parserContext) {
BeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.getPropertyValues().addPropertyValue("namespace","namespace");
return definition;
}
}
修改SpringXML配置文件,修改后内容如下。
完成SpringXML配置文件编写后编写单元测试,具体单元测试代码如下。
@Test
void testXmlCustom() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("META-INF/custom-xml.xml");
UserXsd testUserBean = context.getBean("testUserBean",UserXsd.class);
assert testUserBean.getName().equals("huifer");
assert testUserBean.getIdCard().equals("123");
context.close();
}
在第4章中已经讲述过从namespaceUri转换成NamespaceHandler的过程,相信读者对下面这段代码已经有了一定的了解。
BeanDefinitionHolder decorated =handler.decorate(node,originalDef,new
ParserContext(this.readerContext,this,containingBd));
这段代码的主要目的是调用在测试用例中所编写的UserXsdNamespaceHandler#decorate方法,下面来看经过装饰后的BeanDefinition 对象信息,如图5.26所示。
图5.26装饰后的BeanDefinition
至此,对于BeanDefinition的装饰过程分析完成。
5.4BeanDefinition 细节
本节将会对 BeanDefinition 的各个属性进行介绍,首先阅读BeanDefinition 的类图,如图5.27所示。
图5.27BeanDefinition 类图
从BeanDefinition的类图上可以知道所有BeanDefinition 的根对象是AbstractBeanDefinition,在这个对象中存有BeanDefinition 的大量数据字段。
5.4.1AbstractBeanDefinition属性
下面来看AbstractBeanDefinition的属性,如表5.2所示,列举了AbstractBeanDefinition对象中属性对应的类型和含义。
表5.2AbstractBeanDefinition对象属性表
属 性 名 称
属 性 类 型
属 性 含 义
beanClass
Object
存储 Bean 类型
scope
String
作用域,默认"",常见作用域有 singleton、prototype、request、session和globalsession
lazyInit
Boolean
是否懒加载
abstractFlag
boolean
是否是 abstract 修饰的
autowireMode
int
自动注入方式,常见的注入方式有 no、byName、byType 和 constructor
dependencyCheck
int
依赖检查级别,常见的依赖检查级别有 DEPENDENCY_CHECK_NONE、DEPENDENCY_CHECK_OBJECTS、DEPENDENCY_CHECK_SIMPLE 和 DEPENDENCY_CHECK_ALL
dependsOn
String[]
依赖的 BeanName 列表
autowireCandidate
boolean
是否自动注入,默认值: true
primary
boolean
是否是主要的,通常在同类型多个备案的情况下使用
instanceSupplier
Supplier
Bean实例提供器
续表
属 性 名 称
属 性 类 型
属 性 含 义
nonPublicAccessAllowed
boolean
是否禁止公开访问
lenientConstructorResolution
String
factoryBeanName
String
工厂 Bean 名称
factoryMethodName
String
工厂函数名称
constructorArgumentValues
ConstructorArgumentValues
构造函数对象,可以是 XML 中 constructorarg 标签的解析结果,也可以是 Java 中构造函数的解析结果
propertyValues
MutablePropertyValues
属性列表
methodOverrides
MethodOverrides
重写的函数列表
initMethodName
String
Bean初始化的函数名称
destroyMethodName
String
Bean摧毁的函数名称
enforceInitMethod
boolean
是否强制执行 initMethodName对应的 Java 方法
enforceDestroyMethod
boolean
是否强制执行 destroyMethodName 对应的 Java 方法
synthetic
boolean
合成标记
role
int
Spring中的角色,一般有 ROLE_APPLICATION、ROLE_SUPPORT 和 ROLE_INFRASTRUCTURE
description
String
Bean的描述信息
resource
Resource
资源对象
在AbstractBeanDefinition属性表中beanClass属性的类型是Object,通常情况下会有String类型和Class类型,属性scope默认为单例,属性propertyValues含义为属性列表,它是一个keyvalue结构,用于存储开发者对Bean对象的属性定义,key表示属性名称,value表示属性值。
5.4.2RootBeanDefinition属性
RootBeanDefinition属性详见表5.3,列举了RootBeanDefinition对象中属性对应的类型和含义。
表5.3RootBeanDefinition属性
属 性 名 称
属 性 类 型
属 性 含 义
constructorArgumentLock
Object
构造阶段的锁
postProcessingLock
Object
后置处理阶段的锁
stale
boolean
是否需要重新合并定义
allowCaching
boolean
是否缓存
isFactoryMethodUnique
boolean
工厂方法是否唯一
targetType
ResolvableType
目标类型
resolvedTargetType
Class
目标类型,Bean 的类型
续表
属 性 名 称
属 性 类 型
属 性 含 义
isFactoryBean
Boolean
是否是工厂 Bean
factoryMethodReturnType
ResolvableType
工厂方法返回值
factoryMethodToIntrospect
Method
resolvedConstructorOrFactoryMethod
Executable
执行器
constructorArgumentsResolved
boolean
构造函数的参数是否需要解析
resolvedConstructorArguments
Object[]
解析过的构造参数列表
preparedConstructorArguments
Object[]
未解析的构造参数列表
postProcessed
boolean
是否需要进行后置处理
beforeInstantiationResolved
Boolean
是否需要进行前置处理
decoratedDefinition
BeanDefinitionHolder
BeanDefinition持有者
qualifiedElement
AnnotatedElement
qualified注解信息
externallyManagedConfigMembers
Set
外部配置的成员
externallyManagedInitMethods
Set
外部的初始化方法列表
externallyManagedDestroyMethods
Set
外部的摧毁方法列表
在RootBeanDefinition成员变量中会有一些成对出现的内容,如constructorArgumentsResolved、resolvedConstructorArguments和preparedConstructorArguments,它们用于对构造参数解析状态进行控制,externallyManagedConfigMembers、externallyManagedInitMethods和externallyManagedDestroyMethods,它们用于对外部成员依赖进行控制。
5.4.3ChildBeanDefinition属性
ChildBeanDefinition属性详见表5.4,列举了ChildBeanDefinition对象中属性对应的类型和含义。
表5.4ChildBeanDefinition属性
属 性 名 称
属 性 类 型
属 性 含 义
parentName
String
父BeanDefinition 的名称
ChildBeanDefinition对象是AbstractBeanDefinition的子类,具备AbstractBeanDefinition的所有属性,但是它比AbstractBeanDefinition增加了parentName属性用于指向父BeanDefinition对象。
5.4.4GenericBeanDefinition属性
GenericBeanDefinition属性详见表5.5,列举了GenericBeanDefinition对象中属性对应的类型和含义。
表5.5GenericBeanDefinition属性
属 性 名 称
属 性 类 型
属 性 含 义
parentName
String
父BeanDefinition 的名称
GenericBeanDefinition对象是AbstractBeanDefinition的子类,具备AbstractBeanDefinition的所有属性,但是它比AbstractBeanDefinition增加了parentName属性用于指向父BeanDefinition对象。
5.4.5AnnotatedGenericBeanDefinition属性
AnnotatedGenericBeanDefinition属性详见表5.6,该表
列举了AnnotatedGenericBeanDefinition对象中属性对应的类型和含义。
表5.6AnnotatedGenericBeanDefinition属性
属 性 名 称
属 性 类 型
属 性 含 义
metadata
AnnotationMetadata
注解元信息
factoryMethodMetadata
MethodMetadata
工厂函数的元信息
AnnotatedGenericBeanDefinition对象继承自GenericBeanDefinition对象,在GenericBeanDefinition的基础上增加了注解元信息和工厂函数的元信息变量,这两个变量为Spring注解模式开发中的注解Bean定义提供了充分支持。
小结
在本章中主要对 parseBeanDefinitionElement 方法进行分析 (完整方法签名: org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element,
java.lang.String,org.springframework.beans.factory.config.BeanDefinition)),下面对整个处理过程进行总结,处理过程分为以下12步。
(1) 处理className和parent属性。
(2) 创建基本的 BeanDefinition 对象,具体类: AbstractBeanDefinition、GenericBeanDefinition。
(3) 读取 bean 标签的属性,为 BeanDefinition 对象进行赋值。
(4) 处理描述标签description。
(5) 处理 meta标签。
(6) 处理lookupoverride标签。
(7) 处理replacedmethod标签。
(8) 处理constructorarg标签。
(9) 处理property标签。
(10) 处理qualifier标签。
(11) 设置资源对象。
(12) 设置source属性。
上述12个步骤是Spring中对于bean标签的解析细节,在完成bean标签的解析后Spring会对BeanDefinition对象进行装饰,具体装饰行为操作如下。
(1) 读取标签所对应的 namespaceUri。
(2) 根据 namespaceUri 在 NamespaceHandler 容器中寻找对应的NamespaceHandler。
(3) 调用 NamespaceHandler 所提供的 decorate 方法。
在完成BeanDefinition 对象的创建及其属性赋值后需要对BeanDefinition对象进行注册,有关BeanDefinition 对象的注册内容请查阅第7章。
最后将bean标签解析的入口方法的签名贴出,读者可以根据需求进行查阅。入口签名为org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#processBeanDefinition,入口方法如下。
protected void processBeanDefinition(Element ele,BeanDefinitionParserDelegate delegate) {
//创建BeanDefinition
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//BeanDefinition 装饰
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele,bdHolder);
try {
//注册BeanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().
getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'",ele,ex);
}
//component注册事件触发
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}