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、参考
- spring 官方文档 5.2.3.RELEASE:https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html
- Spring源码深度解析(第2版),郝佳,P52-P57
- 相关注释可参考笔者 github 链接:https://github.com/wpbxin/spring-framework ,本文的案例可参考 spring-context 下的 test 测试案例