在大多数应用程序场景中,容器中的大多数bean都是单例的。当一个单例 bean需要与另一个单例bean协作,或者一个非单例bean需要与其他非单例bean协作时,通常通过将一个bean定义为另一个的属性来处理依赖关系。当bean的生命周期不同时,就会出现问题。假设单例bean A需要使用非单例(原型)bean B,可能是在A上的每个方法调用上。容器只创建一次单例bean A,因此只获得一次设置属性的机会。容器不能在每次需要时都为bean A提供bean B的新实例。
一个解决方案是放弃一些控制权的倒置。您可以通过实现ApplicationContextAware接口,并在每次bean A需要时对容器进行getBean(“B”)调用以请求(通常是新的)bean B实例,使bean A知道容器。以下示例显示了这种方法:
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* A class that uses a stateful Command-style class to perform
* some processing.
*/
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容器的一个稍微高级的特性,可以让您干净地处理这个用例。
lookup方法注入
lookup方法注入是容器重写容器管理的bean上的方法并返回容器中另一个命名bean的查找结果的能力。查找通常涉及一个原型bean,如前一节所述的场景。Spring Framework通过使用CGLIB库中的字节码生成来动态生成覆盖该方法的子类,从而实现了这种方法注入。
- 为了使这种动态子类化工作,Spring bean容器子类化的类不能是final,要重写的方法也不能是final。
- 单元测试具有抽象方法的类需要您自己对该类进行子类化,并提供抽象方法的存根实现。
- 构件扫描也需要具体的方法,这需要具体的类来拾取。
- 另一个关键限制是,查找方法不能与工厂方法一起使用,尤其是配置类中的@Bean方法,因为在这种情况下,容器不负责创建实例,因此不能动态创建运行时生成的子类。
在前面代码片段中的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="myCommand" 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="myCommand"/>
</bean>
标识为commandManager的bean在需要myCommandbean的新实例时调用自己的createCommand()方法。如果实际需要的话,您必须小心地将myCommandbean部署为原型。如果它是一个单例,那么每次都会返回相同的myCommandbean实例。
或者,在基于注释的组件模型中,可以通过@lookup注释声明查找方法,如下例所示:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
或者,更习惯的做法是,您可以依靠目标bean根据查找方法的声明返回类型进行解析:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
请注意,您通常应该使用具体的存根实现来声明此类带注释的查找方法,以便它们与Spring的组件扫描规则兼容,在这些规则中,抽象类默认被忽略。这个限制不适用于显式注册或显式导入的bean类。
访问不同作用域的目标bean的另一种方法是ObjectFactory/Provider注入点。请参阅作为依赖项的作用域Bean。
您还可以发现ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)非常有用。
任意方法替换
与查找方法注入相比,方法注入的一种不太有用的形式是用另一种方法实现替换托管bean中的任意方法的能力。您可以安全地跳过本节的其余部分,直到您真正需要此功能为止。
对于已部署的bean,使用基于XML的配置元数据,可以使用replaced-method元素将现有的方法实现替换为另一个方法实现。考虑下面的类,它有一个名为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"/>
您可以在<replaced method/>元素中使用一个或多个<arg-type/>元素来指示要重写的方法的方法签名。只有当方法重载并且类中存在多个变体时,参数的签名才是必需的。为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下全部匹配java.lang.String:
java.lang.String
String
Str
由于参数的数量通常足以区分每种可能的选择,因此通过只键入与参数类型匹配的最短字符串,此快捷方式可以节省大量键入。