Spring源码笔记-1.2 获取bean流程之bean标签的解析及注册

# 1.解析BeanDefinition

1
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);

作用就是将element封装进bdHolder里面.

其源码很长,不贴了,但功能流程还很清晰

  1. 处理id,name,aliases. 关键是需要beanName ,

    name分隔开做aliases的list.

    beanName优先用id,没id的话就从aliases里remove(0)出来一个,

    如果有beanName了,校验beanName和aliases唯一性

​ 如果还是没beanName,等会再给它用方法生成个

  1. 将element解析,放入GenericBeanDefinition里面
  2. 把上一步拿到的bd连带着aliases转String[], beanName一起封装一下成为BeanDefinitionHolder返回;如果上一步返回null,就直接返回null

这里的关键步骤显然是第二步,element转beanDefinition

1
        AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);

这个方法也很长,大概步骤是

  1. this.parseState.push(new BeanEntry(beanName)) 开头push,结尾pop,不明白是做什么???
  2. 获取下className和parent
  3. 拿着className和parent去创建个GenericBeanDefinition bd
  4. 拿着bd和element进行一系列的解析步骤,把各种值塞入bd
  5. 返回bd
  6. this.parseState.pop();

# 解析各种属性

1
this.parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);

这个方法的代码丧心病狂地达到了70行,相比于目前读到的其它源码,确实是最长的一个.

但是里面的内容其实并不复杂,就是一个个地从element拿属性,塞属性进bd, 各种 get & set .感觉确实不太好重构.

解析了很多属性,包括scope,abstract,lazy-init,autowire…

# 解析子元素meta

# 解析子元素lookup-method

解析方法和meta差别很小,这里主要是去了解下lookup-method的功能

1
2
3
<bean class="beanClass">
    <lookup-method name="method" bean="non-singleton-bean"/>
</bean>

method是beanClass中的一个方法,beanClass和method是不是抽象都无所谓,不会影响CGLIB的动态代理,根据项目实际需求去定义。non-singleton-bean指的是lookup-method中bean属性指向的必须是一个非单例模式的bean,当然如果不是也不会报错,只是每次得到的都是相同引用的bean(同一个实例),这样用lookup-method就没有意义了。

参考: Spring - lookup-method方式实现依赖注入

# 解析子元素replaced-method

这个不仅可以动态地替换返回实体bean,而且还能动态地更改原有方法的逻辑!!!

# 解析子元素constructor-arg

这里提取构造参数的一些属性值就相比之前复杂多了.

  1. 提取index,type,name,判断是否有index属性值
  2. 如果有index:
    1. 构造Entry压入parseState栈
    2. 解析constructor-arg的子元素
    3. 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素
    4. 将index,type,name属性一并封装在ValueHolder中,
    5. 验证index是否用过了,用过则抛错,跳过此条参数
    6. ValueHolder添加至当前BeanDefinition的construArgumentValues的indexedArgurmentValues属性中.
    7. 弹出栈
  3. 如果没有index:
    1. 同上压入栈
    2. 解析constructor-arg的子元素
    3. 使用ConstructorArgumentValues.ValueHolder类型来封装解析出来的元素
    4. 将index,type,name属性一并封装在ValueHolder中,并且添加至当前BeanDefinition的construArgumentValues的genericArgurmentValues属性中.
    5. 弹出栈

可以看出,有没有index其实流程没区别,主要在于在bd中保存的位置不同,另外有index的需要验证下index的唯一

# parsePropertyValue

解析constructor-arg的子元素

1
Object value = this.parsePropertyValue(ele, bd, (String)null);

这个方法也很长,里面的关键在于此处解析constructor-arg下面的子元素时,下面三种情况必须只能占一种

  • 有ref属性
  • 有value属性
  • 有子元素(description和meta除外,这两种不用处理),子元素只能列一个

上面三种情况对应不同的处理方法:

  1. ref:

    校验下ref的值不能为空,然后

    1
    2
    3
    
      RuntimeBeanReference ref = new RuntimeBeanReference(refName);
                ref.setSource(this.extractSource(ele));
                return ref;
    

    用RuntimeBeanReference封装下这个要引用的bean的beanName,塞下resource,返回

    查看BeanReference源码时注意到有RuntimeBeanReference和RuntimeBeanNameReference两种实现,貌似区别不大,RuntimeBeanReference多个toParent的boolean属性,此处给的也是false值.

  2. value:

    1
    2
    3
    
    TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute("value"));
    valueHolder.setSource(this.extractSource(ele));
    return valueHolder;
    

    用TypedStringValue封装下value的值 返回

    TypedStringValue和BeanReference都是实现BeanMetadataElement的,实现了Object getSource();方法

  3. 子元素parsePropertySubElement: 这个又复杂了,判断10+种情况

    • 如果不是默认命名空间,则按其自定义的方式去解析

    • bean元素,按照本文的方式解析,拿到一个BeanDefinitionHolder bdHolder,bdHolder不为空的话,可能还要装饰一下(在这个元素的属性或子元素节点命名空间不为默认情况下, 用自定义方式解析后去装饰),返回最终解析+装饰的结果 bdHolder

    • ref: 主要还是拿要引用的beanName, 取值顺序为其属性 bean–local–parent,和之前的ref一样,用RuntimeBeanReference封装下这个要引用的bean的beanName,塞下resource,返回

    • idref:和ref差不多,取值顺序为bean–local

    • value: 拿文本值,拿type值,没type就用传来的默认typeName,此处为null,然后根据value,typeName和BeanClassLoader来构造一个TypedStringValue,返回

    • null:用null来new一个TypedStringValue,返回

    • array:

      1. 拿value-type和子节点list

      2. 用type和子节点size构建个ManagedArray

        ManagedArray继承ManagedList 继承ArrayList ,

        ManagedList实现Mergeable和BeanMetadataElement ,可以有merge和getSource

        然后设置source和merge属性 (此处还多余重复了一遍setElementTypeName(elementType);),

      3. 然后 this.parseCollectionElements(nl, target, bd, defaultElementType); 对其子节点进行一个个的再解析子元素,还是调用parsePropertySubElement方法,只是带了个默认类型defaultElementType

        看到这已经有点晕了, 最初的那个bd都跑了多少路了?? 子元素里的属性也往bd里直接塞,不就丢失父子结构了么???

      4. list: 和array没什么差别,仅有的区别在于用ManagedList ,(ManagedArray还是继承自他的)

      5. set:同上,仅有的区别在于用ManagedSet,继承自LinkedHashSet

      6. map:这个方法100行,非常长

        1
        2
        3
        4
        5
        6
        
            <constructor-arg name="score">
                <map>
                    <entry key="math" value="90"/>
                    <entry key="english" value="85"/>
                </map>
            </constructor-arg>
        
        1. 拿key-tpye和value-type,子节点entry的list

        2. 构建ManagedMap,设source,KeyTypeName,ValueTypeName,MergeEnable

        3. 遍历entry节点

        4. 拿entry再下一层的子节点,遍历,如果是key节点,则设这个entry的keyEle为此,key只能最多出现一遍, 然后剩下的节点除了description之外最多只能有一个,作为valueEle.所以此处表明这里貌似可以这么写???

           1
           2
           3
           4
           5
           6
           7
           8
           9
          10
          
             <constructor-arg name="score">
                  <map>
                    <entry >
                   	<key>哈哈</key>
                      <vvvaaluue>这样写value??</vvvaaluue>
                      <description>描述随便写,相当于注释</description>
                      <description>描述随便写,相当于注释</description>
                    </entry>
                  </map>
              </constructor-arg>
          

          然后又和解析constructor-arg的子元素类似的情况,三种情况必须只能出现一种:1.“key"属性;2.“key-ref"属性;3.“key"子元素, 三种对应不同的解析

          key:TypedStringValue来封装

          key-ref:RuntimeBeanReference来封装

          key子元素: 里面必须且只能有一个节点,进行parsePropertySubElement,这和在array那看到的一样了

          三种解析后返回的Objext赋值给key

          然后解析value,和上面解析key类似,以下三种必须只能出现一种:

          (有点区别在于value-ref属性和value-type属性不能并存,

          有value-type时就必须要有value属性或者value子节点)

          value属性:拿value-type,没有就用默认的value-type(map节点下的,当然也可能没有),构建TypedStringValue

          value-ref属性:构建RuntimeBeanReference

          value子节点:老样子,还是parsePropertySubElement这样来继续解析子节点

          key,value都拿到之后,会塞入ManagedMap,最终返回ManagedMap

      7. props:

         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        
        <constructor-arg>
                <props>   
                    <prop key="firstName">   
                        Rob   
                    </prop>   
                    <prop key="secondName">   
                        Harrop   
                    </prop>   
                </props>   
        </constructor-arg>
        

        这个就简单了,无非就是遍历props下面的prop元素,拿key属性,拿文本值的trim(),塞入Properties对象,返回这个properties

      8. 至此,解析constructor-arg的子元素算是完成了, 都快忘掉这只是解析constructor-arg里的第一步了…

        总之这一步解析完子元素,返回Object对象回来,作为value

        # ValueHolder

        用上一步返回回来的value,构造个ValueHolder

        ValueHolder类在ConstructorArgumentValues类里,实现了BeanMetadataElement,属性不多,就value,type,name,source,converted,convertedValue

        然后constructor-arg有type属性就给vh塞type,有name塞name, 塞source

        校验完index唯一之后

        1
        
         bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
        

        构造器参数塞入bd的constructorArgumentValues,后面无index的处理方式和这里有index的主要区别就是在往这个constructorArgumentValues里面塞的位置不同.

        BeanDefinition里定义了

        1
        
          ConstructorArgumentValues getConstructorArgumentValues();
        

        这个接口,在AbstractBeanDefinition里有

        1
        
        private ConstructorArgumentValues constructorArgumentValues;
        

        目测专门存放构造器的参数值的

        下面我们来熟悉一下ConstructorArgumentValues这个类吧..

        (这个类实在太麻烦了,写着写着2000多字,重开一文了,即下一篇笔记)

        # 解析子元素property

        1. 拿name属性并且校验
        2. parseState.push()
        3. 校验name在bd.getPropertyValues()里面唯一
        4. parsePropertyValue 提取出property里面的子元素或者ref或者value,这个步骤和之前的解析子元素constructor-arg里面的parsePropertyValue是同一个方法! 只是这里调用带上了propertyName,这里的name是不可能为空的 ,因为第一步校验了
        5. 拿第4步的结果val和propertyName封装一个PropertyValue ,这个也是继承BeanMetadataAttributeAccessor的,
        6. 然后和之前的解析子元素meta一模一样来解析这里面的meta子元素
        7. setSource
        8. propertyValue塞入bd
        9. parseState.pop()

        # 解析子元素Qualifier

        Qualifier通常都是以注解形式使用的,用于在注入bean时明确指明Bean的名称

        1. 拿type属性并校验

        2. parseState.push()

        3. 1
          
          AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName);
          
        4. setSource

        5. 拿value属性,如果有的话就塞入qualifier

        6. 遍历子节点,取其name和value(两者都必须存在),new BeanMetadataAttribute ,塞入qualifier

        7. qualifier塞入bd

        8. parseState.pop()

        # bd设好resource和source,作为结果成功返回.

        # 2.生成beanName

        如果这个bean之前没有id或name,那就为它生成个beanName

        其规则也略烦,不过感觉不重要,简单带过一下,有一种生成规则如下

        命名主要根据的属性有

        getBeanClassName

        父bean名+$child

        工厂名+$created

        ‘class’ nor ‘parent’ nor ‘factory-bean’

        后缀再加上#

        如果是内部bean再就加bd生成的hexString

        如果不是,就再加registry里这种bean的序号 从0开始

        # 3.返回BeanDefinitionHolder

        1
        2
        
        String[] aliasesArray = StringUtils.toStringArray(aliases);
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);