code-learning/spring/15-Spring-IoC 之解析bean标签:constructor-arg、property、qualifier.md

477 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 【死磕 Spring】—— IoC 之解析 <bean> 标签constructor-arg、property、qualifier
**本文主要基于 Spring 5.0.6.RELEASE**
摘要: 原创出处 http://cmsblogs.com/?p=2754 「小明哥」,谢谢!
作为「小明哥」的忠实读者,「老艿艿」略作修改,记录在理解过程中,参考的资料。
------

上篇博客 [《【死磕 Spring】—— IoC 之解析 标签meta、lookup-method、replace-method》](http://svip.iocoder.cn/Spring/IoC-parse-BeanDefinitions-for-meta-and-look-method-and-replace-method) 分析了 `meta``lookup-method``replace-method` 三个子元素。这篇博客,我们来分析 `constructor-arg``property``<qualifier>` 三个子元素。
# 1. constructor-arg 子元素
## 1.1 示例
举个小栗子,代码如下:
```
public class StudentService {
private String name;
private Integer age;
private BookService bookService;
StudentService(String name, Integer age, BookService bookService){
this.name = name;
this.age = age;
this.bookService = bookService;
}
}
<bean id="bookService" class="org.springframework.core.service.BookService"/>
<bean id="studentService" class="org.springframework.core.service.StudentService">
<constructor-arg index="0" value="chenssy"/>
<constructor-arg name="age" value="100"/>
<constructor-arg name="bookService" ref="bookService"/>
</bean>
```
StudentService 定义一个构造函数,配置文件中使用 `constructor-arg` 元素对其配置,该元素可以实现对 StudentService 自动寻找对应的构造函数,并在初始化的时候将值当做参数进行设置。
## 1.2 parseConstructorArgElements
`#parseConstructorArgElements(Element beanEle, BeanDefinition bd)` 方法,完成 `constructor-arg` 子元素的解析。代码如下:
```
// BeanDefinitionParserDelegate.java
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(Element ele, BeanDefinition bd)` 方法,进行解析。
## 1.3 parseConstructorArgElement
```
// BeanDefinitionParserDelegate.java
public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
// 提取 index、type、name 属性值
String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(indexAttr)) {
try {
// 如果有 index
int index = Integer.parseInt(indexAttr);
if (index < 0) {
error("'index' cannot be lower than 0", ele);
} else {
try {
// <1>
this.parseState.push(new ConstructorArgumentEntry(index));
// <2> 解析 ele 对应属性元素
Object value = parsePropertyValue(ele, bd, null);
// <3> 根据解析的属性元素构造一个 ValueHolder 对象
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 {
// <4> 加入到 indexedArgumentValues 中
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 {
try {
this.parseState.push(new ConstructorArgumentEntry());
// 解析 ele 对应属性元素
Object value = parsePropertyValue(ele, bd, null);
// 根据解析的属性元素构造一个 ValueHolder 对象
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));
// 加入到 indexedArgumentValues 中
bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
} finally {
this.parseState.pop();
}
}
}
```
首先获取 `index``type``name` 三个属性值,然后根据是否存在 `index` 来区分,执行后续逻辑。其实两者逻辑都**差不多**,总共分为如下几个步骤(以有 `index` 为例):
1.`<1>` 处,构造 ConstructorArgumentEntry 对象并将其加入到 ParseState 队列中。ConstructorArgumentEntry 表示构造函数的参数。
2.`<2>` 处,调用 `#parsePropertyValue(Element ele, BeanDefinition bd, String propertyName)` 方法,解析 `constructor-arg` 子元素,返回结果值。详细解析,见 [「1.4 parsePropertyValue」](https://svip.iocoder.cn/Spring/IoC-parse-BeanDefinitions-for-constructor-arg-and-property-and-qualifier/#) 。
3.`<3>`根据解析的结果值构造ConstructorArgumentValues.ValueHolder 实例对象,并将 `type``name` 设置到 ValueHolder 中
4.`<4>` 处,最后,将 ValueHolder 实例对象添加到 `indexedArgumentValues` 集合中。
`index` 的处理逻辑差不多,只有几点不同:
- 构造 ConstructorArgumentEntry 对象时是调用**无参**构造函数
- 最后是将 ValueHolder 实例添加到 `genericArgumentValues` 集合中。
## 1.4 parsePropertyValue
调用 `#parsePropertyValue(Element ele, BeanDefinition bd, String propertyName)` 方法,解析 `constructor-arg` 子元素,返回结果值。代码如下:
```
/**
* 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");
// <1> 查找子节点中,是否有 ref、value、list 等元素
// 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);
// meta 、description 不处理
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;
}
}
}
// <1> 是否有 ref 属性
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
// <1> 是否有 value 属性
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
// <1> 多个元素存在,报错,存在冲突。
if ((hasRefAttribute && hasValueAttribute) || // 1. ref 和 value 都存在
((hasRefAttribute || hasValueAttribute) && subElement != null)) { // 2. ref he value 存在一,并且 subElement 存在
error(elementName +
" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
}
// <2> 将 ref 属性值,构造为 RuntimeBeanReference 实例对象
if (hasRefAttribute) {
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;
// <3> 将 value 属性值,构造为 TypedStringValue 实例对象
} else if (hasValueAttribute) {
TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
valueHolder.setSource(extractSource(ele));
return valueHolder;
// <4> 解析子元素
} else if (subElement != null) {
return parsePropertySubElement(subElement, bd);
} else {
// Neither child element nor "ref" or "value" attribute found.
error(elementName + " must specify a ref or value", ele);
return null;
}
}
```
1.
```
<1>
```
处,提取
```
constructor-arg
```
的子元素、
```
ref
```
属性值和
```
value
```
属性值,对其进行判断。以下两种情况是不允许存在的:
1. `ref` 和 `value` 属性同时存在 。
2. 存在 `ref` 或者 `value` 且又有子元素。
2. 在 `<2>` 处,若存在 `ref` 属性,则获取其值并将其封装进 `org.springframework.beans.factory.config.RuntimeBeanReference` 实例对象中。
3. 在 `<3>` 处,若存在 `value` 属性,则获取其值并将其封装进 `org.springframework.beans.factory.config.TypedStringValue` 实例对象中。
4. 在 `<4>` 处,如果子元素不为空,则调用 `#parsePropertySubElement(Element ele, BeanDefinition bd)` 方法,对子元素进一步解析。详细解析,见 [「1.5 parsePropertySubElement」](https://svip.iocoder.cn/Spring/IoC-parse-BeanDefinitions-for-constructor-arg-and-property-and-qualifier/#) 中。
## 1.5 parsePropertySubElement
对于 `constructor-arg` 子元素的嵌套子元素,需要调用 `#parsePropertySubElement(Element ele, BeanDefinition bd)` 方法,进一步处理。
```
/**
* 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)) { // bean 标签
BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd);
if (nestedBd != null) {
nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd);
}
return nestedBd;
} else if (nodeNameEquals(ele, REF_ELEMENT)) { // ref 标签
// 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.
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;
} else if (nodeNameEquals(ele, IDREF_ELEMENT)) { // idref 标签
return parseIdRefElement(ele);
} else if (nodeNameEquals(ele, VALUE_ELEMENT)) { // value 标签
return parseValueElement(ele, defaultValueType);
} else if (nodeNameEquals(ele, NULL_ELEMENT)) { // null 标签
// 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;
}
}
```
上面对各个子类进行分类处理,详细情况,如果各位有兴趣,可以移步源码进行深一步的探究。本文,暂时不做深入分析。
# 2. property 子元素
## 2.1 示例
我们一般使用如下方式,来使用 `property` 子元素。
```
<bean id="studentService" class="org.springframework.core.service.StudentService">
<property name="name" value="chenssy"/>
<property name="age" value="18"/>
</bean>
```
## 2.2 parsePropertyElements
对于 property 子元素的解析Spring 调用 `parsePropertyElements(Element beanEle, BeanDefinition bd)` 方法。代码如下:
```
/**
* 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)) { // property 标签
parsePropertyElement((Element) node, bd);
}
}
}
```
和 `constructor-arg` 子元素差不多,同样是“提取”( 遍历 )所有的 `property` 的子元素,然后调用 `#parsePropertyElement((Element ele, BeanDefinition b)` 进行解析。
## 2.3 parsePropertyElement
```
/**
* Parse a property element.
*/
public void parsePropertyElement(Element ele, BeanDefinition bd) {
// 获取 name 属性
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 {
// 如果存在相同的 name ,报错
if (bd.getPropertyValues().contains(propertyName)) {
error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
return;
}
// 解析属性值
Object val = parsePropertyValue(ele, bd, propertyName);
// 创建 PropertyValue 对象
PropertyValue pv = new PropertyValue(propertyName, val);
parseMetaElements(ele, pv);
pv.setSource(extractSource(ele));
// 添加到 PropertyValue 集合中
bd.getPropertyValues().addPropertyValue(pv);
} finally {
this.parseState.pop();
}
}
```
与解析 `constructor-arg` 子元素步骤差不多:
- 调用 `#parsePropertyElement((Element ele, BeanDefinition b)` 方法, 解析子元素属性值。
- 然后,根据该值构造 PropertyValue 实例对象。
- 最后,将 PropertyValue 添加到 BeanDefinition 中的 MutablePropertyValues 中。
# 3. qualifier 子元素
> 老艿艿:小明哥貌似忘记写 `<qualifier>` 标签的解析,不过实际情况下也用的比较少,所以老艿艿,在这里稍微简单写下。
>
> 不感兴趣的胖友,可以直接跳过这块内容。
## 3.1 示例
见 [《Spring 注解实现Bean依赖注入之 @Qualifier》](http://www.voidcn.com/article/p-vdgrbkrm-bqu.html) 文章。
## 3.2 parseQualifierElement
`#parseQualifierElement(Element ele, AbstractBeanDefinition bd)` 方法,完成 `qualifier` 子元素的解析。代码如下:
```
/**
* Parse a qualifier element.
*/
public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) {
// 解析 type 属性
String typeName = ele.getAttribute(TYPE_ATTRIBUTE);
if (!StringUtils.hasLength(typeName)) { // 必须有 type
error("Tag 'qualifier' must have a 'type' attribute", ele);
return;
}
this.parseState.push(new QualifierEntry(typeName));
try {
// 创建 AutowireCandidateQualifier 对象
AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName);
qualifier.setSource(extractSource(ele));
// 解析 value 属性,并设置到 AutowireCandidateQualifier 中
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)) { // attribute 标签
Element attributeEle = (Element) node;
String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE); // attribute 标签的 key 属性
String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE); // attribute 标签的 value 属性
if (StringUtils.hasLength(attributeName) && StringUtils.hasLength(attributeValue)) {
// 创建 BeanMetadataAttribute 对象
BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue);
attribute.setSource(extractSource(attributeEle));
// 添加到 attributes 中
qualifier.addMetadataAttribute(attribute);
} else {
error("Qualifier 'attribute' tag must have a 'name' and 'value'", attributeEle);
return;
}
}
}
// 添加到 qualifiers 中
bd.addQualifier(qualifier);
} finally {
this.parseState.pop();
}
}
```
代码比较简单,胖友自己研究下哈。