方法注入(Method injection)
Spring提供两种机制去注入方法,分别是 Lookup method inject,Arbitrary method replacement。Lookup method inject只提供返回值注入,Arbitrary method replacement可以替换任意方法来达到注入。
Lookup method inject
有时我们需要在一个bean A中调用另一个bean B的方法,通常我们会添加一个字段,然后使用依赖注入把bean B的实例注入到这个字段上。这种情况下在bean A 和 bean B都是singleton时没问题,但是在 bean A是singleton和bean B是非singleton时就可能出现问题。因为bean B为非singleton , 那么bean B是希望他的使用者在一些情况下创建一个新实例,而bean A使用字段把bean B的一个实例缓存了下来,每次都使用的是同一个实例。
我们假设bean B是一个prototype
一种解决办法是不使用字段依赖注入,每次使用bean B的时候都去bean容器中重新获取
/**
* 这是一个命令执行类,提供一个process方法,执行用户的命令
*/
public class CommandManager{
//使用依赖注入,把applicationContext注入进来
@Autowire
private ApplicationContext applicationContext;
/**
* 根据用户指定的参数,每次使用一个新的Command实例去执行命令
* @param commandState 执行的参数
* @return 执行后的返回值
*/
public Object process(Map commandState) {
// 每次使用都调用createCommand去获取一个新实例
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
//从applicationContext获取一个新的Command实例
protected Command createCommand() {
return this.applicationContext.getBean("command", Command.class);
}
}
上面的代码是每次都从applicationContext重新获取一个新实例来实现的。Spring提供了一个Lookup method inject机制,它可以改变方法的返回值,来达到方法注入的效果。对应的有annotation和xml两种使用方式。
annotation的使用方式@Lookup,把@Lookup加到你要改变方法返回值的方法上
public abstract class CommandManager{
public Object process(Map commandState) {
// 每次使用都调用createCommand去获取一个新实例
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
/**
* Loopup的注释中的写明了需要返回的bean名字,如果没有写bean name,那么会根据createCommand的函数返回值类型去查找对应的bean
* @return
*/
@Lookup("command")
protected abstract Command createCommand();
}
Spring的Lookup method inject实现原理的是使用CGLIB动态生成一个类去继承CommandManager
,重写createCommand
方法。然后根据@Lookup中指定的bean Name或者createCommand
方法的返回类型判断需要返回的bean。createCommand
可以是abstract和可以不是。因为使用的是继承,所以CommandManager
类和createCommand
方法都不能是final的。
createCommand
方法的签名需要满足如下要求
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
对应实现的XML配置
<bean id="command" class="AsyncCommand" scope="prototype">
</bean>
<bean id="commandManager" class="CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>
访问不同Scope的bean,可以使用ObjectFactory
/ Provider
注入,详情查看Scoped beans as dependencies.
Arbitrary method replacement
Lookup method inject只是改变了方法的返回值,但是method replacement可以替换bean 容器里任意方法的实现,达到方法的完全注入,一般情况下不要这个使用特性!
此特性,只能基于XML配置实现。假如我们要替换如下类的computeValue
方法
public class MyValueCalculator {
public String computeValue(String input) {
//...真实代码
}
}
第一步,我们要现实org.springframework.beans.factory.support.MethodReplacer
接口
public class ReplacementComputeValue implements MethodReplacer {
/**
* 当我们替换的方法被调用时,容器就会代理到这里,在这里执行我们要替换的执行逻辑
* @param o 替换方法执行时对应的实例
* @param m 替换方法
* @param args 替换方法执行时传入的参数
* @return
* @throws Throwable
*/
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
String input = (String) args[0];
...
return ...;
}
}
第二步,在XML中,使用replaced-method
元素进行配置.
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- 需要替换的方法 -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
在上面的xml中,在元素replaced-method
中使用了arg-type
。它的作用是在有多个方法重载时,根据arg-type
中指定的参数class名字来确定具体替换哪一个方法。arg-type
中的值可以是类全路径的一个子串,如下面所有的值都可以匹配java.lang.String
java.lang.String
String
Str