写点什么

Spring(六),终于找到一个看得懂的 JVM 内存模型了

  • 2021 年 11 月 10 日
  • 本文字数:4385 字

    阅读完需:约 14 分钟

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()


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


;


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) {

评论

发布
暂无评论
Spring(六),终于找到一个看得懂的JVM内存模型了