In most application scenarios, most beans in the container are singletons. When a singleton bean needs to collaborate with another singleton bean, or a non-singleton bean needs to collaborate with another non-singleton bean, you typically handle the dependency by defining one bean as a property of the other. A problem arises when the bean lifecycles are different. Suppose singleton bean A needs to use non-singleton (prototype) bean B, perhaps on each method invocation on A. The container only creates the singleton bean A once, and thus only gets one opportunity to set the properties. The container cannot provide bean A with a new instance of bean B every time one is needed.
A solution is to forego some inversion of control. You can make bean A aware of the container by implementing the ApplicationContextAware
interface, and by making a getBean("B") call to the container ask for (a typically new) bean B instance every time bean A needs it. The following is an example of this approach:
在大多数应用场景中,容器中的大多数bean都是 singletons。 当单例bean需要与另一个单例bean协作或非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个的属性来处理依赖关系。不过对于具有不同生命周期的bean 来说这样做就会出现问题。 假设单例bean A需要使用非单例(原型)bean B,也许在A上的每个方法调用上。容器仅创建单例bean A一次,因此只有一次机会来设置属性。 这样就没办法 在需要的时候每次让容器为bean A提供一个新的bean B实例。
解决方案是放弃一些控制的反转。 您可以通过实现以下操作来让bean A
知道容器 ApplicationContextAware
接口,并通过对容器调用getBean(“B”)在每次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;
}
}
The preceding is not desirable, because the business code is aware of and coupled to the Spring Framework. Method Injection, a somewhat advanced feature of the Spring IoC container, allows this use case to be handled in a clean fashion.
You can read more about the motivation for Method Injection in this blog entry.
前面是不可取的,,因为业务代码和Spring框架产生的耦合。方法注入,作为Spring Ioc容器的高级特性,,允许以干净的方式处理这个用例。
你可以在这个博客查看阅读更多关于方法注入的动机
Lookup method injection
查找方法注入具有使容器覆盖受容器管理的bean 上的方法的能力,从而可以返回容器中另一个命名bean的查找结果。 查找方法注入适用于原型bean,如前一节中所述的场景。 Spring框架通过使用从CGLIB库生成的字节码来动态生成覆盖该方法的子类来实现此方法注入。
为了使这个动态子类化起作用,Spring bean容器将继承的类不能是final ,被重写的方法也不能是final 。对一个具有abstract 方法的类进行单元测试需要你为其写一个子类并提供该抽象方法 的实现。Concrete(具体)方法对于组件扫描也是必要的,这需要具体的类来拾取。另一个关键的限制是,查找方法将不能使用工厂方法, 特别是在配置类中不使用@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在需要myCommand
bean的新实例时,将调用自己的createCommand()
方法。 将myCommand
bean 设置成prototype,如果这是实际上需要的话,一定要谨慎处理。 如果它是一个 singleton,那么每次将返回相同的myCommand
bean。
或者,在基于注解的组件模型中,您可以通过“@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) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
注意,您通常会使用具体的子类实现来声明这种带注解的查找方法,以使它们与Spring的组件扫描规则兼容,默认情况下会忽略抽象类。 此限制不适用于显式注册或显式导入的bean类的情况。
另一种访问不同范围的目标bean的方法是ObjectFactory / Provider 注入点。 查看the section called “Scoped beans as dependencies”。感兴趣的读者也可以找到ServiceLocatorFactoryBean (在org.springframework.beans.factory.config 包中)来用一用。 |
任意方法替换
与查找方法注入相比,很少有用的方法注入形式是使用另一个方法实现替换托管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"/>
您可以在<replaced-method/>
元素中使用一个或多个包含的<arg-type/>
元素来指示要覆盖的方法的方法签名。 仅当方法重载并且类中存在多个变量时,参数的签名才是必需的。 为了方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。 例如,以下所有匹配java.lang.String
:
java.lang.String
String
Str
因为参数的数量通常足以区分每个可能的选择,这个简写方式可以节省大量的输入,允许你只需要输入最短字符串就可匹配参数类型。