日积月累,水滴石穿 😄
lookup-method
通常称为获取器注入,spring in action 中对它的描述是,一种特殊的方法注入,它是把一个方法声明为返回某种类型的 bean,而实际要返回的 bean 是在配置文件里面配置的,可用在设计可插拔的功能上,解除程序依赖。
实例
- 首先创建一个父类,并编写一个方法 eat()。
/**
* 父类:水果
*/
public class Fruit {
public void eat(){
System.out.println("吃什么水果呢");
}
}
2、然后创建一个子类,继承父类,并重写父类的方法。
/**
* 子类:苹果
*/
public class Apple extends Fruit {
@Override
public void eat(){
System.out.println("吃苹果");
}
}
3、创建调用方法
public abstract class TestLookupMethod {
public abstract Fruit getBean();
/**
* 这个方法的创建不会对于 LookupMethod 的覆盖不会有任何影响
* 这个参数 Spring 并不会去处理它
* 影响的地方也就是在 createBean() 里的 prepareMethodOverrides() 方法了,
* overloaded 属性不会被设置为 false 了。
AbstractBeanDefinition#prepareMethodOverride
protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {
// 获取对应类中对应方法的个数
int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());
if (count == 0) {
throw new BeanDefinitionValidationException(
"Invalid method override: no method with name '" + mo.getMethodName() +
"' on class [" + getBeanClassName() + "]");
}
else if (count == 1) {
// Mark override as not overloaded, to avoid the overhead of arg type checking.
//标记 overloaded 暂未被覆盖 避免参数类型检查的开销
mo.setOverloaded(false);
}
}
*/
public abstract Fruit getBean(String str);
public void start(){
this.getBean().eat();
}
}
4、然后编写配置文件
<bean id="fruit" class="com.gongj.lookupMethod.Fruit"></bean>
<bean id="apple" class="com.gongj.lookupMethod.Apple"></bean>
<bean class="com.gongj.lookupMethod.TestLookupMethod" id="lookupMethod">
// name 值为 getBean
<lookup-method name="getBean" bean="apple"></lookup-method>
</bean>
5、进行测试
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("lookup-method.xml");
TestLookupMethod bean =(TestLookupMethod) context.getBean("lookupMethod");
// 调用 start 方法
bean.start();
}
输出结果:吃苹果
当业务变更或者在其它情况下, apple
里面的业务逻辑已不再符合我们的业务要求,需要进行替换怎么办 ?这时我们需要新增逻辑类。
/**
* 子类:香蕉
*/
public class Banana extends Fruit{
@Override
public void eat(){
System.out.println("正在吃香蕉");
}
}
然后只需修改配置类,新增名为 banana
的 Bean,并将 lookup-method
标签中配置的 apple
修改为 banana
。再次调用测试方法。
<bean class="com.gongj.lookupMethod.Banana" id="banana"></bean>
<bean class="com.gongj.lookupMethod.TestLookupMethod" id="lookupMethod">
<lookup-method name="getBean" bean="banana"></lookup-method>
</bean>
Spring 框架通过使用 CGLIB 库中的字节码来动态生成覆盖该方法的子类,从而实现此方法注入。所以被覆盖的类不能为 final,并且要被覆盖的方法也不能为 final 。还需注意 scope 的配置,如果 scope 配置为 singleton,则每次调用 getBean 方法 ,返回的对象都是相同的;如果 scope 配置为 prototype,则每次调用返回都不同。
在包含要注入的方法的 TestLookupMethod
类中,要注入的方法(getBean)
需要以下形式的签名:
<public|protected>
[abstract] <return-type>
theMethodName(no-arguments);
- public|protected:要求方法必须是可以被子类重写和调用的。
- abstract:可选,如果是抽象方法,CGLIB 的动态代理类就会实现这个方法,如果不是抽象方法,就会覆盖这个方法。
- return-type:返回类型,当然可以是它的父类或者接口。
- no-arguments:不允许有参数。
到这,我们已经会使用 lookup-method
这个标签了,接下来我们就结合源码去看。直接进入到BeanDefinitionParserDelegate
类的 parseLookupOverrideSubElements
方法。
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
// 获取该bean节点下所有子节点
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;
// 获取 name 属性的值,也就是要修饰的方法
String methodName = ele.getAttribute(NAME_ATTRIBUTE);
// 获取 bean 属性的值,也就是配置返回的bean 名称
String beanRef = ele.getAttribute(BEAN_ELEMENT);
// 根据 methodName、beanRef 构建 LookupOverride 对象
LookupOverride override = new LookupOverride(methodName, beanRef);
override.setSource(extractSource(ele));
// 将该对象添加到 MethodOverrides对象里的 overrides集合中
overrides.addOverride(override);
}
}
}
replaced-method
方法替换:可以在运行时用新的方法替换现有的方法,与之前的 look-up
不同的是,replaced-method
不但可以动态地替换返回实体 bean ,而且还能动态地更改原有方法的逻辑。我们先来看任何使用。
实例
1、再次新创建一个类,里面提供两个重载方法。
public class Replaced {
public void replacedName(String name){
System.out.println("我是初版:"+name);
}
public void replacedName(String name,Integer age){
System.out.println("我是初版:" + name + "年年:"+ age);
}
}
2、编写一个实现了 MethodReplacer
的类
public class TestReplacedMethod implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
for (Object arg : args) {
System.out.println("接收参数:" + arg);
}
return null;
}
}
3、修改配置文件
<bean class="com.gongj.lookupMethod.Replaced" id="replaced">
<replaced-method name="replacedName" replacer="replacedMethod">
<!--<arg-type></arg-type>可以配置任意多个 取决于想替换哪个重载方法-->
<arg-type>String</arg-type>
<arg-type>Integer</arg-type>
</replaced-method>
</bean>
<!-- replacedMethod -->
<bean class="com.gongj.lookupMethod.TestReplacedMethod" id="replacedMethod"></bean>
4、测试类
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("replace-method.xml");
Replaced bean =(Replaced) context.getBean("replaced");
bean.replacedName("gongj",88);
}
输出结果:
接收参数:gongj
接收参数:88
运行测试类就可以看到预期的结果了,也就是做到了动态替换原有方法,知道了这个元素的用法,再来看元素的提取过程。
直接进入到BeanDefinitionParserDelegate
类的 parseReplacedMethodSubElements
方法。
public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
// 获取该bean节点下所有子节点
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 属性的值,也就是要被替换的旧方法
String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
// 获取 replacer 属性的值,也就是新的替换方法
String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
// 根据 name、replacer 构建 ReplaceOverride 对象
ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
// Look for arg-type match elements. 寻找arg-type元素
List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
for (Element argTypeEle : argTypeEles) {
// 记录参数
String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
if (StringUtils.hasText(match)) {
replaceOverride.addTypeIdentifier(match);
}
}
replaceOverride.setSource(extractSource(replacedMethodEle));
// 将该对象添加到 MethodOverrides对象里的 overrides集合中
overrides.addOverride(replaceOverride);
}
}
}
我们可以看到无论是 lookup-method
还是 replaced-method
都是构造了 MethodOverride
,并最终记录在了 AbstractBeanDefinition
中的 methodOverrides
属性中。
- 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。