zoukankan      html  css  js  c++  java
  • Spring5源码分析(015)——IoC篇之解析bean标签:meta、lookup-method、replaced-method

    注:《Spring5源码分析》汇总可参考:Spring5源码分析(002)——博客汇总

    关于相关标签的使用和说明,建议参考最正宗的来源——官方参考文档:https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html

    注:文档内容比较多,建议按照关键字进行快速搜索。


      bean 标签的属性解析完之后,接下来需要解析的就是各种子元素了,从代码中可以看出,总共有 6 类子元素,分别是 :meta、lookup-method、replaced-method、constructor-arg、property、qualifier。

      本文将对前面 3 个子元素的解析进行分析,这3个子元素的作用如下(具体示例和说明可参考后文的分析):

    • <meta/> :元数据,BeanDefinition 内部定义的 key-value ,按需取用,并不会在 bean 中体现。
    • <lookup-method/> :获取器注入,是一种特殊的方法注入,它是把一个方法声明为返回某种类型的 bean ,但实际要返回的 bean 是在配置文件里面配置的,此方法可用于在设计有些可插拔的功能上,解除程序依赖。
    • <replaced-method/> :方法替代,可以在运行时用新的方法替换现有的方法。与 lookup-method 不同的是,replaceed-method 不但可以动态地替换返回实体 bean ,而且还能动态地更改原有方法的逻辑。

    本文目录结构如下:

    1、解析 meta 子元素

      在开始分析前,我们先看下元数据 meta 子元素的使用。

    <bean id= "myTestBean" class="cn.wpbxin.MyTestBean">
        <meta key="testStr" value="metaStringTest" />
    </bean>

      这段代码并不会体现在 myTestBean 的属性当中,而是一个额外的声明(直观点就当成是 key-value ),当需要使用里面的信息的时候,可以通过 BeanDefinition 的 getAttribute(key) 方法进行获取。

      对 meta 子元素的解析代码如下:

    /**
     * Parse the meta elements underneath the given element, if any.
     * 解析给定元素下的 meta 子元素(如果有的话的)
     */
    public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) {
        NodeList nl = ele.getChildNodes();
        // 遍历子节点, meta 可能存在多个
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            // 提取 meta 标签
            if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) {
                Element metaElement = (Element) node;
                // 提取 key 和 value
                String key = metaElement.getAttribute(KEY_ATTRIBUTE);
                String value = metaElement.getAttribute(VALUE_ATTRIBUTE);
                // 使用 key 和 value 构造 BeanMetadataAttribute
                BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value);
                // 添加到 attributeAccessor ,即 AbstractBeanDefinition
                attribute.setSource(extractSource(metaElement));
                attributeAccessor.addMetadataAttribute(attribute);
            }
        }
    }

      解析过程也是比较直接了当,遍历获取到 meta 子元素,提取 key 、value 值,然后封装成 BeanMetadataAttribute ,之后调用 BeanMetadataAttributeAccessor.addMetadataAttribute(BeanMetadataAttribute attribute) 方法将构造的 BeanMetadataAttribute 添加到 AbstractBeanDefinition 中。

    • 注:AbstractBeanDefinition 继承了 BeanMetadataAttributeAccessor ,而 BeanMetadataAttributeAccessor 继承了 AttributeAccessorSupport 。BeanMetadataAttributeAccessor 在 AttributeAccessorSupport 记录并持有 attributes 的基础上还增加了对 attributes 来源的记录。

      attributes 的 add 和 get 操作如下:

    /// org.springframework.beans.BeanMetadataAttributeAccessor
    /**
     * Add the given BeanMetadataAttribute to this accessor's set of attributes.
     * @param attribute the BeanMetadataAttribute object to register
     */
    public void addMetadataAttribute(BeanMetadataAttribute attribute) {
        super.setAttribute(attribute.getName(), attribute);
    }
    
    
    /// org.springframework.core.AttributeAccessorSupport
    /** Map with String keys and Object values. */
    private final Map<String, Object> attributes = new LinkedHashMap<>();
    
    @Override
    public void setAttribute(String name, @Nullable Object value) {
        Assert.notNull(name, "Name must not be null");
        if (value != null) {
            this.attributes.put(name, value);
        }
        else {
            removeAttribute(name);
        }
    }
    
    @Override
    @Nullable
    public Object getAttribute(String name) {
        Assert.notNull(name, "Name must not be null");
        return this.attributes.get(name);
    }

    2、解析 lookup-method 子元素

      lookup-method :获取器注入,是一种特殊的方法注入,它是把一个方法声明为返回某种类型的 bean ,但实际要返回的 bean 是在配置文件里面配置的,此方法可用于在设计有些可插拔的功能上,解除程序依赖。接下来我们看一个具体例子。

    2.1、lookup-method 使用示例

      代码如下:

    package cn.wpbxin.bean.lookupmethod;
    
    public class User {
    
        public void lookupMethod() {
            System.out.println("This is the User's lookupMethod!");
        }
    }

    /// 子类1
    package cn.wpbxin.bean.lookupmethod; public class Teacher extends User{ @Override public void lookupMethod() { System.out.println("This is the Teacher's lookupMethod!"); } } /// 子类2 package cn.wpbxin.bean.lookupmethod; public class Student extends User{ @Override public void lookupMethod() { System.out.println("This is the Student's lookupMethod!"); } } /// 需要获取器注入的 bean package cn.wpbxin.bean.lookupmethod; public abstract class LookupMethodBean { public void showResult() { this.getBean().lookupMethod(); } public abstract User getBean(); } /// 测试类 package cn.wpbxin.bean.lookupmethod; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class LookupMethodTest { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("cn/wpbxin/bean/lookupmethod/spring-lookup-method.xml"); LookupMethodBean test = (LookupMethodBean) applicationContext.getBean("lookupMethodBean"); test.showResult(); } }

      代码到此基本完成,还差下配置。这里可能会有点疑问:这是抽象类和抽象方法,不可以直接调用吧? 如果使用 Spring 获取器的配置的话,是可以做到的, XML 配置文件如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="lookupMethodBean" class="cn.wpbxin.bean.lookupmethod.LookupMethodBean">
            <lookup-method name="getBean" bean="student" />
        </bean>
        <bean id="teacher" class="cn.wpbxin.bean.lookupmethod.Teacher" />
        <bean id="student" class="cn.wpbxin.bean.lookupmethod.Student" />
    </beans>

      配置中,我们看到了前面提到的 lookup-method 子元素配置,这个配置完成的功能是动态地将 student 所代表地 bean 作为 getBean 地返回值,运行 main 测试方法后可以看到控制台输出了:

    This is the Student's lookupMethod!

      当业务变更或者其他场景下,如果 student 里面地业务逻辑已经不再符合业务要求,那么我们可以这样进行替换,增加新地逻辑类 Teacher ,然后对配置进行更改:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="lookupMethodBean" class="cn.wpbxin.bean.lookupmethod.LookupMethodBean">
            <lookup-method name="getBean" bean="teacher" />
        </bean>
        <bean id="teacher" class="cn.wpbxin.bean.lookupmethod.Teacher" />
        <bean id="student" class="cn.wpbxin.bean.lookupmethod.Student" />
    </beans>

      再次运行 main 测试方法,可以看到这次输出是

    This is the Teacher's lookupMethod!

      至此,我们初步了解了 lookup-method 子元素所提供的大致功能了,这时候再来看相关的解析源码时应该会更清晰、更有针对性。

    2.2、parseLookupOverrideSubElements

      lookup-method 子元素的解析,是通过 BeanDefinitionParserDelegate.parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) 来进行处理,代码如下:

    /**
     * Parse lookup-override sub-elements of the given bean element.
     */
    public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
        NodeList nl = beanEle.getChildNodes();
        // 遍历子节点
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            // 默认的 lookup-method 标签
            if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
                Element ele = (Element) node;
                // 获取对应的方法
                String methodName = ele.getAttribute(NAME_ATTRIBUTE);
                // 获取配置的返回 bean
                String beanRef = ele.getAttribute(BEAN_ELEMENT);
                // 创建对应的 LookupOverride 对象
                LookupOverride override = new LookupOverride(methodName, beanRef);
                override.setSource(extractSource(ele));
                // 添加到 methodOverrides 中
                overrides.addOverride(override);
            }
        }
    }

      这里的解析和 meta 子元素的解析类似。遍历找到对应的子元素,然后解析其中的属性,这里时 methodName 、 banRef ,然后构造对应的 LookupOverride 对象,并添加到 AbstractBeanDefinition 的 methodOverrides 属性中,需要注意的是,这里仅仅只是完成标记。

    3、解析 replaced-method 子元素

      replaced-method 方法替代:可以在运行时用新的方法替换现有的方法。与之前的 lookup-method 不同的是,replaceed-method 不但可以动态地替换返回实体 bean ,而且还能动态地更改原有方法的逻辑。这里还是先举个例子看看 replaced-method 的用法

    3.1、replaced-method 使用示例

      代码如下:

    /// 原先的 bean 和逻辑
    package cn.wpbxin.bean.replacedmethod;
    
    public class ReplacedMethodBean {
        public void replacedMethod() {
            System.out.println("This is the replaced method!");
        }
    }
    
    /// 新的逻辑
    package cn.wpbxin.bean.replacedmethod;
    
    import org.springframework.beans.factory.support.MethodReplacer;
    
    import java.lang.reflect.Method;
    
    public class RMethodReplacer implements MethodReplacer {
        @Override
        public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
            System.out.println("Now this is the replacer method!");
            return null;
        }
    }
    
    /// 测试类
    package cn.wpbxin.bean.replacedmethod;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class ReplacedMethodTest {
        public static void main(String[] args) {
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("cn/wpbxin/bean/replacedmethod/spring-replaced-method.xml");
            ReplacedMethodBean replacedMethodBean = (ReplacedMethodBean)applicationContext.getBean("replacedMethodBean");
            replacedMethodBean.replacedMethod();
        }
    }

      完成逻辑替换的配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
       <bean id="replacedMethodBean" class="cn.wpbxin.bean.replacedmethod.ReplacedMethodBean">
          <replaced-method name="replacedMethod" replacer="replacer" />
       </bean>
    
       <bean id="replacer" class="cn.wpbxin.bean.replacedmethod.RMethodReplacer" />
    </beans>

      运行测试 main 方法,最终输出如下,可以看到,这里完成了对原先方法的实时替换:

    Now this is the replacer method!

    3.2、parseReplacedMethodSubElements

      看完演示示例再来理解 replaced-method 的解析就会比较直接直观些了,这里是通过 BeanDefinitionParserDelegate.parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) 来进行解析:

    /**
     * Parse replaced-method sub-elements of the given bean element.
     */
    public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
        NodeList nl = beanEle.getChildNodes();
        // 遍历子节点
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            // 默认的 replaced-method 标签
            if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
                Element replacedMethodEle = (Element) node;
                // 获取需要被替换的方法名 name 和 新的替换类 replacer
                String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
                String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
                // 创建 ReplaceOverride 对象
                ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
                // 获取 arg-type ,对应的是方法重载 Overload 中的参数列表
                // Look for arg-type match elements.
                List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
                for (Element argTypeEle : argTypeEles) {
                    // 获取 match 参数
                    String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
                    match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
                    // 记录参数到 ReplaceOverride 中
                    if (StringUtils.hasText(match)) {
                        replaceOverride.addTypeIdentifier(match);
                    }
                }
                replaceOverride.setSource(extractSource(replacedMethodEle));
                // 添加到 methodOverrides 中
                overrides.addOverride(replaceOverride);
            }
        }
    }

      这里的解析也比较直观,读取的是 name 和 replacer 属性,然后构建 ReplaceOverride 对象,之后记录到 AbstractBeanDefinition 中的 methodOverrides 属性中,和 lookup-method 一样的操作。另外需要注意的是,因为存在方法重载 overload,参数列表是不一样的,所以这里还对 arg-type 进行了解析,作为参数列表。当然,这里也仅仅只是完成标记。后续将对 MethodOverrides 和实例化 bean 时再进行相关的详细分析。

    4、总结

      本文主要是对 bean 标签的3个子元素 meta、lookup-method、replaced-method 的使用和解析进行了简要的说明和分析。据笔者经验,这3个子元素在实际场景中使用可能不多。后面2个标签的解析其实只是做了个标记,并没有进行是的处理,后续分析 Bean 实例化时会再做进一步的详细说明。

    5、参考

  • 相关阅读:
    Git--记一次丢失本地记录但是代码已提交到gerrit
    IDEA--IDEA配置web项目
    JavaSE--泛型
    Cassandra--Cassandra 安装
    Git--.gitignore
    org.springframework.test.context.junit4.SpringJUnit4ClassRunner
    散列·跳房子散列
    散列·布谷鸟散列
    散列·完美散列
    散列·再散列
  • 原文地址:https://www.cnblogs.com/wpbxin/p/13551225.html
Copyright © 2011-2022 走看看