Method injection更容易理解的翻译方式是通过方法注入。先不说什么是method injection,先通过一个问题(从官方文档翻译而来),引入method injection。
提出问题
一个单例bean A,如果依赖非单例的bean B(B被注入到A中);对于A来说,B就是一个单例的bean。因为不论使用构造器、setter或工厂方法注入,只会注入一次,现在想要的结果就是A对B的每次使用,都希望新实例化一个B。
解决方式一:放弃一部分IoC,与Spring耦合(不推荐使用)
就拿上面的问题来说,如果A依赖于Spring容器(A保存一个容器的引用),A每次使用B时,都通过容器的getBean("B")来获取一个新的实例。
public class CommandManager {
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);
}
}
看这个例子,每次CommandManager调用process的时候,都会调用creatCommand,也就是说从容器中取一个非单例的bean。
到此为止,上面的例子还不能运行成功,因为还没有给CommandMananger注入applicationContext;这又是一个问题,对于applicationContext的注入,不应该放到容器外(通过其他类)完成,所以,Spring提供了一个ApplicationContextAware接口
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
官方对于这个接口的描述:
When an ApplicationContext creates an object instance that implements the org.springframework.context.ApplicationContextAware interface, the instance is provided with a reference to that ApplicationContext.
也就是说,当容器初始化一个实现了ApplicationContextAware接口的bean时,会把容器的引用,通过接口中的setter方法注入到bean中。
所以,完整的例子如下:
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容器产生了耦合
- 如果没有源文件(没办法实现接口),这样的方式无法解决问题
解决方式二:Lookup Method Injection
Lookup method injection is the ability of the container to override methods on container-managed beans and return the lookup result for another named bean in the container.
上面是官方文档的解释,也就是说,容器通过重写一个bean的方法,返回另一个bean(被依赖的非单例bean)。
The Spring Framework implements this method injection by using bytecode generation from the CGLIB library to dynamically generate a subclass that overrides the method.
Spring框架通过CGLIB(代码生成库)动态地生成重写了方法的字节码。
修改上面的例子,现在,要把createCommand()方法交给容器重写
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();
}
配置元数据如下:<lookup-method/>
指出要重写的方法以及方法返回的bean。
<!-- 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>
这个配置的含意:Spring框架重写createCommand()方法,这个方法返回myCommand。
注:如果上面的myCommand这个bean是单例bean,则createCommand每次返回同一个对象。(一般没人这样做)
被重写的方法的签名必须符合下面的要求:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
上面的例子是一个抽象方法,被重写的方法可以是具体的方法。
除了使用XML配置,还可以使用注解的方式
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
Lookup("myCommand")
指出这个方法要被重写,返回id/name为myCommand的bean。
除了使用id/name指定要返回的bean,Spring还可以根据方法的返回类型自动匹配bean:
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
要重写的方法是createCommand,它的返回值类型为MyCommand,所以,会返回容器中类型为MyCommand的bean。
总结
Method Injection,通过方法注入依赖。就像上面的Lookup method,通过这个方法得到另一个bean的引用。
一个bean可以使用Lookup Method来得到它依赖的bean,从而不用字段来保存另一个bean的引用。