在大多数应用场景中,容器中的大多数beans是singleton的。当一个singleton bean需要与另一个singleton bean合作时,或者一个非singleton bean与另一个非singleton bean合作时,一般处理这种依赖,是在另一个中定义一个property的bean。当这个bean的声明周期不同的时候问题就来了。假设singleton bean A需要使用非singleton bean B(property),可能在A的每个方法调用上。容器仅仅创建singleton bean A 一次,并且只有一次机会来设置其属性。容器不能每次需要bean B的时候提供给bean A一个bean B的实例。
一种解决方案是放弃某些控制反转。你可以实现ApplicationContextAware接口使得容器的bean A意识到,并且通过调用getBean使得容器在每次bean A需要的时候请求bean B的实例。例子如下:
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
这种处理方式不建议使用,因为业务代码与Spring框架耦合了。方法注入,Spring IoC容器的某点高级功能,允许在一个干净的方式中使用这种情况。
lockup method injection(锁死方法注入)
锁死方法注入是容器重写所管理beans方法的能力,返回容器中另一个方法bean的锁死结果。锁死在前面提到的章节中一般地涉及到一个property bean。Spring框架通过使用来自CGLB包的字节码产生器实现这个方法注入来产生动态的子类,重写这个方法。
注意:为使这个动态子类工作,其父类不能是final类型,并且需重写的方法也不能是fianl。如果父类中有abstract类型的方法也需要你自己实现。最后,已经是方法注入的目标的对象不能被序列化。在Spring3.2中,不再将CGLB添加到你的系统路径中,因为CGLB已经打包在Spring核心包的org.springframe内。这样做也是为了避免其他使用CGLB版本的潜在冲突。
查看前面提到的CommandManager代码片段,发现Spring容器动态重写了createCommand()方法的实现。你的commandManager类将没有一些Spring依赖,这可以在改造的例子中看到。
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
在包含了要注入方法的客户端类(CommandManager),要注入的方法需要如下的标签格式:
public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果这个方法是抽象类型的,动态创建的子类需要实现这个方法。否则,动态创建的子类重写原始类的这个具体方法。例如:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>
无论什么时候 commandManager需要一个command实例时,其调用自己的createCommand方法。你必须部署这个command bean为prototype,如果确实是这样需要的。如果部署为singleton,每次返回时都是command bean的同一个实例。
Arbitrary method replacement(任意方法替代)
相较于lookup 方法注入好一点的方法注入是可以用另一个方法实现替代管理bean的任意方法。
用基于XML配置的元数据,可以使用replaced-method元素用另一个方法替换一个已经存在的方法实现,这针对一个部署的bean。考虑下面的类,有computeValue方法,需要我们重写:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
实现了org.springframework.beans.factory.support.MethodReplacer
接口的类提供了新方法的定义。
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
这个bean定义部署在原始类中,并制定了需要重写的方法:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
你可以使用<arg-type/>元素来指定需要重写的方法签名。仅当重载了这个方法并且类中有多个变量,参数签名就很必要了。为了方便,String类型可能是String权限定名的子字符串。例如:
java.lang.String
String
Str
因为参数数量通常足以区分开来。这种简写可以大量的输入,允许你输入最短的字符串来匹配类型。