Spring(六),终于找到一个看得懂的 JVM 内存模型了
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 标签了
<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) {
评论