回顾
前面已经看了父Bean标签对下面几种标签的解析
- meta标签
- lookup-method子标签
- replaced-method子标签
- construct-arg子标签(构造器注入)
这几种标签,其实不都怎么常用,通常我们使用最多的肯定是property标签
下面就来看看property标签是怎样被解析的
解析property标签
在配置文件中,我们一般这样使用property标签
<bean id="person" class="service.Person">
<!-- 控制器调用setAxe方法,将容器中的axe bean作为传入的参数 -->
<!--此处的name是决定Person类中的那个参数,ref是指bean配置文件中的bean名称-->
<property name="axe" ref="axe"></property>
</bean>
仍然回到我们创建Bean的流程图里面(BeanDefinitionParserDelegate完成)
/**
* 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));
//获取class属性
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
//获取parent属性
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
//下面就是解析其他属性的
try {
//创造BeanDefinition实体(也就是bean承载)
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//硬编码解析默认bean的各种属性
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//下面进行一些bean的属性进行加工解析
//解析元数据
parseMetaElements(ele, bd);
//解析lookup-method属性
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//解析replaced-method属性
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析构造函数的参数
parseConstructorArgElements(ele, bd);
//解析property子元素
parsePropertyElements(ele, bd);
//解析qualified子元素
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;
}
parsePropertyElements方法
源码如下
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
//获取Bean标签的所有子标签
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)) {
//如果为property子标签
//那就去执行parsePropertyElement方法
parsePropertyElement((Element) node, bd);
}
}
}
parsePropetyElement方法
public void parsePropertyElement(Element ele, BeanDefinition bd) {
//获取property标签里面的name属性
//name属性其实就是成员属性名字
String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
//判断name属性是否为空
if (!StringUtils.hasLength(propertyName)) {
//为空就报错,并直接结束
error("Tag 'property' must have a 'name' attribute", ele);
return;
}
//记录此时状态为加载property标签里面的name属性
this.parseState.push(new PropertyEntry(propertyName));
try {
//判断当前的bean是否已经注册过这个property的name
//其实就是判断当前的bean对该成员属性是否已经记录过
if (bd.getPropertyValues().contains(propertyName)) {
//如果已经记录过,那么就报错
//因为重复加载了,不可能有两个成员属性的变量名是一样的
error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
return;
}
//继续解析property里面的属性和子标签
Object val = parsePropertyValue(ele, bd, propertyName);
//解析完后,将property标签里面的所有内容,封装为PropertyValue对象
//包括name和val其他属性
PropertyValue pv = new PropertyValue(propertyName, val);
//解析property标签下面的meta子标签!
parseMetaElements(ele, pv);
pv.setSource(extractSource(ele));
//记录进当前bean里面,的propertyValues里面
//代表这个Name的成员属性已经加载过了
bd.getPropertyValues().addPropertyValue(pv);
}
finally {
this.parseState.pop();
}
}
parsePropertyValue方法
该方法就是解析value子标签和ref子标签的!
value子标签就是代表成员属性待注入的值,而ref子标签为成员属性待注入的另外一个bean!(即成员属性是一个对象,那么可以使用ref来为这个成员属性进行注入)
源码如下(是不是感觉很熟悉,这个解析property标签的,与解析construct-arg标签的value子标签是一样的!)
@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.
//按照约定,construct-arg应该只有一个子标签
NodeList nl = ele.getChildNodes();
Element subElement = null;
//遍历子标签
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//筛选出不是meta和description子标签的
//所以construct-arg是可以有多个子标签的,但只能是meta和description
//且倘若出现 ref, value, list, etc其中一个,后面的meta和description会导致报错
if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
!nodeNameEquals(node, META_ELEMENT)) {
// Child element is what we're looking for.
//筛选了之后,就代表这是我们想要的子标签
//这也说明了一个现象,在construct-arg中.meta和description子标签是失效的!
//下面这个判断就很有意思了
//它是限制了constrcuct-arg里面只能有一个非meta、description的子标签
//即外层if判断为true只能进来一次
//第一次会为null,但第二次循环如果还能进来就不再为null了
if (subElement != null) {
//设置了多个子标签,报错
error(elementName + " must not contain more than one sub-element", ele);
}
else {
//从这次循始将要结束时,subElement就不再为null了
//同时这里也是记录了value子标签或其他子标签
subElement = (Element) node;
}
}
}
//获取construct-arg的ref和value属性
boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
//假如两个同时拥有,或者两个属性拥有一个,但解析出的子标签不为null,证明有子标签
//就代表出现了重复给构造参数了
if ((hasRefAttribute && hasValueAttribute) ||
((hasRefAttribute || hasValueAttribute) && subElement != null)) {
//重复给构造参数,报错处理
error(elementName +
" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
}
//如果并没有重复,假如是ref属性
if (hasRefAttribute) {
String refName = ele.getAttribute(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
error(elementName + " contains empty 'ref' attribute", ele);
}
//ref属性用RuntimeBeanReference对象存储
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
//标明来源
ref.setSource(extractSource(ele));
//返回
return ref;
}
//如果是value属性
else if (hasValueAttribute) {
//使用TypedStringValue进行存储
TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
valueHolder.setSource(extractSource(ele));
//返回
return valueHolder;
}
//如果是子标签形式
else if (subElement != null) {
//交由parsePropertySubElement继续处理子标签
//因为子标签也是有多种形式的
return parsePropertySubElement(subElement, bd);
}
//既没有value属性,又没有ref属性,又没有子标签
else {
// Neither child element nor "ref" or "value" attribute found.
error(elementName + " must specify a ref or value", ele);
return null;
}
}
这里就不再赘述了
PropertyValue是怎样被记录的?
前面不是说了,对于property标签,是会被放进去PropertyValues对象里面的(与Construct-arg标签被存放进ConstructArgumentValues对象一样)
BeanDefinition这个接口只有一个实现类就是AbstractBeanDefinition,也就是在AbstractBeanDefinition中,就初始化了这个存储容器了(与ConstructorArgumentValues一样),可知AbstractBeanDefinition有着记录创建Bean的细节
可以见到,其本质是MutablePropertyValues对象
而且其底层的容器有两个
- PropertyValueList:一个ArrayList集合,并且默认容量为0,这个ArrayList就是用来存储PropertyValue的
- proccessedProperties:一个String的集合,这个就是用来去重的!也就是原先判断这个property是否正在注册(通过name属性判断,),里面存储的就是name属性,本质上是一个HashSet并且初始化的容量为4
不过,决定这个property是否可以存放进来,是由这两个容器共同决定的
从代码上可以看到,判断这个property是否已经加载过,需要满足2个条件
- 遍历PropertyValueList,里面没有一个propertyValue的name是与其一样的
- 且proccessedProperties里面为空或者proccessedProperties没有这个propertyName记录
满足上面两个条件,才会返回false,代表这个property没有被加载过
下面再看看添加propertyValue进propertyValueList的逻辑
public MutablePropertyValues addPropertyValue(PropertyValue pv) {
//遍历存储propertyValue的propertyValueList
for (int i = 0; i < this.propertyValueList.size(); i++) {
PropertyValue currentPv = this.propertyValueList.get(i);
//如果出现了重复PropertyName
if (currentPv.getName().equals(pv.getName())) {
//判断是否需要合并处理
pv = mergeIfRequired(pv, currentPv);
//合并处理后重新修改这个propertyValue
setPropertyValueAt(pv, i);
return this;
}
}
//没有出现重复的propertyName,直接往propertyValueList里面添加
this.propertyValueList.add(pv);
return this;
}
可以看到,spring的逻辑真的严谨,前面已经判断过一次了,这里又要进行判断(一般来说,应该不会走去合并处理的)
合并的逻辑
private PropertyValue mergeIfRequired(PropertyValue newPv, PropertyValue currentPv) {
Object value = newPv.getValue();
//如果value实现了Mergeable接口
//那就进行合并!
if (value instanceof Mergeable) {
Mergeable mergeable = (Mergeable) value;
if (mergeable.isMergeEnabled()) {
//value进行合并后,产生新的propertyValue并返回
Object merged = mergeable.merge(currentPv.getValue());
return new PropertyValue(newPv.getName(), merged);
}
}
//没实现Mergeable接口,直接返回旧值
return newPv;
}
解析qualifier标签
终于到最后一个qualifier标签了
<bean id="animal" class="test.constructor.Animal">
<qualifier type="test.qualifier.Person" value="student"></qualifier>
</bean>
对于qualifier我们通常都是使用注解@Qualifier的,那么这个qualifier标签有什么用呢?
要知道,在使用Spring框架中进行自动注入的时候,Spring容器中提供的候选Bean必须有而且仅仅只能有一个,当找不到匹配的Bean时,Spring容器将抛出BeanCreationException
qualifier标签就是用来定义这个bean的别名的,代表这个bean必须根据名称(ByName)才会被选为候选bean(一般时ByType),即根据bean的名称进行注入!
/**
* Parse qualifier sub-elements of the given bean element.
*/
public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
//同理,遍历所有子标签
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//遇到qualifier标签就执行方法,parseQualifierElement
if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ELEMENT)) {
parseQualifierElement((Element) node, bd);
}
}
}
parseQualifierElement方法
public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) {
String typeName = ele.getAttribute(TYPE_ATTRIBUTE);
//判断type属性是否为空
//type属性是
if (!StringUtils.hasLength(typeName)) {
error("Tag 'qualifier' must have a 'type' attribute", ele);
return;
}
//开始跟踪
this.parseState.push(new QualifierEntry(typeName));
try {
//使用AutowireCandidateQualifier去存储qualifier标签
AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName);
qualifier.setSource(extractSource(ele));
String value = ele.getAttribute(VALUE_ATTRIBUTE);
if (StringUtils.hasLength(value)) {
qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, value);
}
NodeList nl = ele.getChildNodes();
//对Qualifier子标签进行解析!
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属性和value属性
String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE);
String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE);
if (StringUtils.hasLength(attributeName) && StringUtils.hasLength(attributeValue)) {
BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue);
attribute.setSource(extractSource(attributeEle));
qualifier.addMetadataAttribute(attribute);
}
else {
error("Qualifier 'attribute' tag must have a 'name' and 'value'", attributeEle);
return;
}
}
}
//当前bean去存储qualiier
bd.addQualifier(qualifier);
}
finally {
this.parseState.pop();
}
}
可以看到,存储Qualifier标签对象的是一个LinkedHashMap
至此,Bean标签解析就完成了