在大多数应用场景中,容器中的大多数bean都是单例的。当一个单例bean需要与另一个单例bean协作,或者一个非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖关系。当bean的生命周期不同时,就会出现问题。假设单例bean A需要在每次调用A的方法时使用非单例(原型)bean B。容器只创建一次单例bean A,因此只有一次设置属性的机会。容器不能在每次需要时为bean A提供一个新的bean B实例。
一种解决方案是放弃一些控制反转。你可以通过实现ApplicationContextAware
接口使bean A意识到容器,并在每次bean A需要时通过调用getBean("B")
向容器请求(通常新的)bean B实例。下面的例子展示了这种方法:
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 Method Injection)
查找方法注入是容器覆盖容器管理bean上的方法并返回容器中另一个命名bean的查找结果的能力。查找通常涉及原型bean,如前一节中描述的场景。Spring框架通过使用CGLIB库的字节码生成来实现这种方法注入,以动态生成一个覆盖该方法的子类。
- 要使这种动态子类化工作,Spring bean容器要子类化的类不能是
final
的,要被覆盖的方法也不能是final
的。 - 单元测试一个含有
abstract
方法的类要求你自己将该类进行子类化,并提供abstract
方法的一个桩实现。 - 具体方法对于组件扫描也是必要的,它需要具体的类来获取。
- 另一个关键的局限性是查找方法不适用于工厂方法,尤其是在配置类中的
@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);
如果方法是abstract
的,那么动态生成的子类将实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。考虑以下示例:
<!-- 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>
每当需要myCommand
bean的新实例时,被标识为commandManager
的bean会调用其自身的createCommand()
方法。如果确实需要将myCommand
bean部署为原型,则必须谨慎操作。如果它是单例,则每次都返回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) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
请注意,通常应该使用具体的桩实现(stub implementation)来声明这类带注解的查找方法,以便它们与Spring的组件扫描规则兼容,其中抽象类默认会被忽略。这个限制不适用于显式注册或显式导入的bean类。
访问不同作用域的目标bean的另一种方式是使用ObjectFactory
/Provider
注入点。
你可能还会发现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"/>
你可以在元素内使用一个或多个<arg-type/>
元素来指示被覆盖方法的方法签名。只有在方法是重载的,并且在类中存在多个变体时,才需要参数的签名。为了方便起见,参数的类型字符串可以是全限定类型名称的子字符串。例如,以下所有内容都匹配java.lang.String
:
java.lang.String
String
Str
因为参数的数量通常足以区分每个可能的选择,这个快捷方式可以通过让你只键入匹配参数类型的最短字符串来节省大量的打字工作。