Spring1.1中引入了一个很有用的IOC新特性--方法注入(Method Injection)。这能让我们在组装bean时获得极大的灵活性。Spring的方法注入可以分为两种形式:查询方法注入(Lookup Method Injection)和方法替换(Method Replacement)。查询方法注入提供了另一种机制来让bean获取它依赖的其它bean,方法替换则可以在不修改源代码的基础上,修改任意bean的任何方法的实现。为了提供这两个功能,Spring借助了CGLIB包。
先来看看查询方法注入的使用。想象这样的情形:我们有一个singleton类型的bean A,它需要使用另一个非singleton类型的bean B来执行一些操作。由于两个bean的生命周期是不同的,因此我们不能简单的在bean A的配置中使用ref标签来要求Spring注入bean B,因为那样会让我们每次取得bean A时都使用同一个bean B的实例。回想前面介绍过的BeanFactoryAware接口,我们可以让bean A实现该接口,这样就可以在bean A内部使用beanFactory的genBean方法来获取bean B了。只要我们将bean B配置为非singleton类型,每次使用getBean方法就会得到一个新的bean B的实例。
使用查询方法注入,我可以不必实现任何的Spring的接口,也不需要在bean A中显示的使用getBean方法来获得bean B。我们只需要在bean A中申明一个查询方法,然后在bean A的配置文件中指明该查询方法,那么Spring就会自动的将bean B注入到bean A中去了。由于查询方法注入的概念相对比较复杂,因此我们还是通过具体的例子来直观的感受它是如何工作的。
在这个例子中,我们创建一个非singleton的bean和两个实现了同一个接口的singleton的bean,一个通过传统的设值方法注入获得非singleton的bean,另一个则通过查询方法注入。
//The MyHelper Bean
public class MyHelper {
public void doSomethingHelpful() {
// do something!
}
}
//The DemoBean Interface
public interface DemoBean {
public MyHelper getMyHelper();
public void someOperation();
}
//The StandardLookupDemoBean
public class StandardLookupDemoBean implements DemoBean {
private MyHelper helper;
public void setMyHelper(MyHelper helper) {
this.helper = helper;
}
public MyHelper getMyHelper() {
return this.helper;
}
public void someOperation() {
helper.doSomethingHelpful();
}
}
//The AbstractLookupDemoBean
public abstract class AbstractLookupDemoBean implements DemoBean {
public abstract MyHelper getMyHelper();
public void someOperation() {
getMyHelper().doSomethingHelpful();
}
}
<beans>
<bean id="helper" class="com.apress.prospring.ch5.mi.MyHelper" singleton="false"/>
<bean id="abstractLookupBean"
class="com.apress.prospring.ch5.mi.AbstractLookupDemoBean">
<lookup-method name="getMyHelper" bean="helper"/>
</bean>
<bean id="standardLookupBean"
class="com.apress.prospring.ch5.mi.StandardLookupDemoBean">
<property name="myHelper">
<ref local="helper"/>
</property>
</bean>
</beans>
可以看到,我们使用了lookup-method来对查询方法注入进行配置,name属性告诉Spring需要覆写bean中的哪个方法,该方法必须是没有参数的,并且返回类型是我们需要获得的bean的类型,bean属性告诉Spring查询方法需要返回哪个bean。
通过DemoBean standardBean = (DemoBean)factory.getBean("standardLookupBean");获得standardBean,然后比较standardBean.getMyHelper() == standardBean.getMyHelper(),可以发现,直接使用设置方法注入,每次获得的非singleton的bean实际上是同一个实例。而使用查询方法注入,则会每次得到一个新的实例,这实际上是因为我们使用factory.getBean("abstractLookupBean")获得abstractBean时,Spring根据lookup-method的配置,覆写了查询方法,从而返回一个新的bean实例,正如前面提到的,这是通过CGLIB包来实现的。
值得注意的是,我们将查询方法定义为抽象的,这不是必须的,但这么做能防止我们忘记了配置lookup-method,从而使用了空的(当然不是我们期望的)查询方法。
好了,让我们再来看看方法替换,虽然它被划分为方法注入的一种,但它和我们以前接触到的注入方式有着很大的差异,以前我们都是注入一个bean,而方法替换则是替换bean中的方法实现。同查询方法注入一样,我们还是通过一个例子来理解方法替换的使用。
//The ReplacementTarget Class
public class
ReplacementTarget {
public String formatMessage(String msg) {
return "<h1>" + msg + "</h1>";
}
public String formatMessage(Object msg) {
return "<h1>" + msg + "</h1>";
}
}
现在由于某些原因,我们需要修改以String为参数的方法的实现方式,并且不能修改ReplacementTarget类的源代码,借助于Spring的方法替换,我们可以轻松地实现。
首先,创建一个实现了MethodReplacer接口的类。
public class FormatMessageReplacer implements MethodReplacer {
public Object reimplement(Object target, Method method, Object[] args)
throws Throwable {
...
String msg = (String) args[0];
return "<h2>" + msg + "</h2>";
...
}
}
<beans>
<bean id="methodReplacer"
class="com.apress.prospring.ch5.mi.FormatMessageReplacer"/>
<bean id="replacementTarget"
class="com.apress.prospring.ch5.mi.ReplacementTarget">
<replaced-method name="formatMessage" replacer="methodReplacer">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="standardTarget"
class="com.apress.prospring.ch5.mi.ReplacementTarget"/>
</beans>
通过replaced-method标签来配置需要替换的方法,name属性指明了需要替换的方法名,replacer属性指明了用哪个bean来实现替换操作。arg-type是用来指明需要替换的方法的参数类型的,在bean中有重载方法时(像ReplacementTarget类一样),指明参数类型可以让Spring明确知道需要替换的是哪个方法。值得注意的是,arg-type是模式匹配的,因此,String将匹配String和StringBuffer。
方法替换在很多场合都是有用的,比如我们只想为一个特殊的bean改变它所依赖的另一个bean的实现方法,而不影响其它bean的引用。最好是为每个需要替换的方法,或者是一组重载的方法定义一个替换类,而不要为众多毫不相关的方法使用同一个替换类。这可以减少很多没必要的比较,从而提高代码的执行效率。