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="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 wpbxin.bean.lookupmethod;

public class User {

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

/// 子类1
public class Teacher extends User{

    @Override
    public void lookupMethod() {
        System.out.println("This is the Teacher's lookupMethod!");
    }
}

/// 子类2
package wpbxin.bean.lookupmethod;

public class Student extends User{

    @Override
    public void lookupMethod() {
        System.out.println("This is the Student's lookupMethod!");
    }
}

/// 需要获取器注入的 bean
package wpbxin.bean.lookupmethod;

public abstract class LookupMethodBean {

    public void showResult() {
        this.getBean().lookupMethod();
    }
    public abstract User getBean();
}

/// 测试类
package 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("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="wpbxin.bean.lookupmethod.LookupMethodBean">
        <lookup-method name="getBean" bean="student" />
    </bean>
    <bean id="teacher" class="wpbxin.bean.lookupmethod.Teacher" />
    <bean id="student" class="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="wpbxin.bean.lookupmethod.LookupMethodBean">
        <lookup-method name="getBean" bean="teacher" />
    </bean>
    <bean id="teacher" class="wpbxin.bean.lookupmethod.Teacher" />
    <bean id="student" class="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 wpbxin.bean.replacedmethod;

public class ReplacedMethodBean {
    public void replacedMethod() {
        System.out.println("This is the replaced method!");
    }
}

/// 新的逻辑
package 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 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("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="wpbxin.bean.replacedmethod.ReplacedMethodBean">
      <replaced-method name="replacedMethod" replacer="replacer" />
   </bean>

   <bean id="replacer" class="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、参考

 

上一篇:【Java基础】方法调用机制——MethodHandle


下一篇:Spring Boot 整合——MongoDB整合4(MongoDB分组去重以及MongoDB联表查询)