通常情况下,我们都是把一个单例bean注入到另一个单例bean,这不会有作用域的问题。
但是不同作用域的Spring Bean之间互相依赖,如果不做特殊定义,则只能在实例化Spring Bean时注入其所依赖的其他Spring Bean。比如以下定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在这个例子中,将会话域的userPreferences bean注入进了单例域userManager。userManager是个单例bean,它在每个Spring容器中只会被初始化一次,它的依赖对象,也就是userPreferences bean也只会被注入一次。这就意味着userManager每次操作的都是在最开始注入进来的那一个userPreferences对象。
这显然不是我们想要的效果。
当我们把一个短生命周期的bean注入至一个长生命周期的bean时,我们希望有个唯一的userManager单例对象,在每个HTTP Session的生命周期内,都能有一个专门的userPreferences对象供userManager使用。
AOP代理注入
<!-- 将一个HTTP Session bean暴露为一个代理bean -->
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<!-- 通知Spring容器去代理这个bean -->
<aop:scoped-proxy/>
</bean>
<!-- 将上述bean的代理注入到一个单例bean -->
<bean id="userService" class="com.foo.SimpleUserService">
<property name="userPreferences" ref="userPreferences"/>
</bean>
使用以上配置,Spring容器会创建一个代理类, 使之与UserPreferences 类实现相同的接口,理论上这个代理类也是一个UserPreferences对象,并能根据作用域去获得真正的UserPreferences对象。Spring容器将这个代理类的对象注入到userManager,但是userManager并不清楚它获得的UserPreferences其实是个代理。当UserManager实例调用注入的UserPreferences对象的某个方法时,实际上调用的是代理对象的方法。然后,代理对象从HTTP Session中获取,并将方法调用委派给真正的UserPreferences对象。
默认情况下,对于使用了<aop:scoped-proxy/>
元素的bean,Spring容器会创建一个基于CGLIB代理机制的代理类。不过CGLIB代理只能拦截public方法,所以不要在代理类上调用非public方法。
Lookup方法注入
package fiona.apple;
// CommandManager类没有任何的Spring依赖
public abstract class CommandManager {
public Object process(Object commandState) {
// 取得一个Command的新实例
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
// 定义为抽象方法,该方法返回的是command bean的实例,由Spring容器自动复写和调用
protected abstract Command createCommand();
}
<!-- 原型的bean -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- 注入相关的依赖 -->
</bean>
<bean id="commandManager" class="fiona.apple.CommandManager">
<!-- commandManager bean在需要一个新的command bean实例时会调用createCommand()方法 -->
<lookup-method name="createCommand" bean="command"/>
</bean>
Lookup方法注入的内部机制是Spring利用了CGLIB库在运行时生成二进制代码的功能,通过动态创建Lookup方法bean的子类从而达到复写Lookup方法的目的。