6.4.6 方法注入
在大多数应用程序场景中,容器中的大多数bean都是单例。 当单例bean需要与另一个单例bean协作,或者非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖关系。 当bean生命周期不同时会出现问题。假如单例Bean A需要使用非单例Bean B,也许在A上的每次方法调用。容器仅创建一次单例A,因此只有一次机会设置属性。当使用的时候,容器不能每次提供一个新的Bean B给A。
一种解决方案是,放弃一部分控制反转。你可以把Bean A实现ApplicationContextAware接口,并且在A每次需要的时候调用容器getBean(“B”)方法获取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容器的一个高级功能,它允许以简洁的方式处理这个用例。
你可以在此博客条目中阅读有关方法注入动机的更多信息。
Lookup方法注入
Lookup 方法注入是容器的一种能力,覆写容器管理的Bean的方法,为容器中另外一个命名Bean返回lookup结果。
在前面章节描述方案中,Lookup一般作为原型bean。Spring框架通过CGLIB库使用字节码生成器动态生成一个覆写方法的子类。
注意:
- 类不能是final,而且覆写方法不能是final
- Unit测试类,具有abstract方法,要求你自我实现子类以提供abstract方法的stub实现
- 具体方法对于要求查找具体类的组件扫描是必要的
- 更关键的限制是lookup方法对于工厂方法不生效,尤其对于配置类使用@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(); }
对于包含方法注入的客户端类,方法注入要求如下形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是abstract,动态生成的子类就实现此方法;否则,动态生成子类覆写原始类定义的具体方法。例如:
<!-- 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>
不管什么时候,Id为commandManager的Bean需要command Bean的新实例,会调用它自己的createCommand()方法。如果在确实需要的情况下,你一定要注意的是把command Bean作为原型Bean去部署。如果作为单例去部署,每次就会返回相同的command Bean。
注意:感兴趣的读者也许会发现ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)的使用,ServiceLocatorFactoryBean使用方法跟另外一个工具类,ObjectFactoryCreatingFactoryBean相似,但是它允许你指定你自己lookup接口,这正好与Spring规范的lookup接口相反。查阅这些类的java doc文档获取其他信息。
替换任意方法
方法注入比lookup方法注入多一点用的是能够通过另外一种方法实现替换被容器管理的Bean的arbitrary方法。用户可以安全地跳过剩下的部分直到功能性确实需要的情况下。
基于XML配置元数据,你可以给部署的Bean,使用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/>的元素,表示被覆写的方法签名。如果类中有重载方法和多变体存在,参数签名是有必要的。为了方便,String类型参数可以是类型全称的子串,例如,以下的都匹配java.lang.String:
java.lang.String String Str
正因为参数数目常常足够去辨别每种可能的选择,所以通过允许你录入最短的字符串去匹配参数类型,简写能节省很多录入工作。