当两个singleton作用域Bean存在依赖关系时,或prototype作用域Bean依赖singleton作用域Bean时,不会有任何问题。
但当singleton作用域Bean依赖prototype作用域Bean时,singleton作用域Bean只有一次初始化的机会,它的依赖关系也只在初始化阶段被设置,它所依赖的prototype作用域Bean则需要每次都得到一个全新的Bean实例,这将会导致singleton作用域的Bean的依赖得不到即时更新。
singleton Bean依赖于prototype Bean时,当Spring容器初始化时,它会初始化容器中所有singleton Bean,此时prototype Bean被创建出来,并注入到singleton Bean中------当singleton Bean创建完成后,它就持有了一个prototype Bean,容器再不会为singleton Bean执行注入了。
由于singleton Bean具有单例行为,当客户端多次请求singleton Bean时,Spring返回给客户端的将是同一个singleton Bean实例,这不存在任何问题。问题是:如果客户端多次请求singleton Bean、并调用singleton Bean去调用prototype Bean的方法时,始终都是调用同一个prototype Bean实例,这就违背了设置prototype Bean的初衷:本来希望它具有prototype行为,但实际上它却表现出singleton行为。
这就是问题的所在:当singleton作用域的Bean依赖于prototype作用域的Bean时,会产生不同步的现象。
解决该问题有如下两种思路:
① 部分放弃依赖注入:singleton作用域Bean每次需要prototype作用域Bean时,主动向容器请求新的Bean实例,即可保证每次注入的prototype Bean实例都是最新的实例。
② 利用方法注入。
第一种方式显然不是一个好的做法,代码主动请求新的Bean实例,必然导致代码与Spring API耦合,造成代码严重污染。
通常情况下,我们采用第二种做法,使用方法注入。
方法注入通常使用lookup方法注入,利用lookup方法注入可以让Spring容器重写容器中Bean的抽象或具体方法,返回查找容器中其他Bean的结果,被查找的Bean通常是一个non-singleton Bean。Spring通过使用CGLIB库修改客户端的二进制码,从而实现上述的要求。
Axe.java :
public interface Axe {
public String chop();
}
SteelAxe.java :
public class SteelAxe implements Axe {
@Override
public String chop() {
return "钢斧砍柴真快";
}
public SteelAxe() {
System.out.println("Spring实例化依赖Bean:SteelAxe实例...");
}
}
Person.java :
public interface Person {
public void useAxe();
}
Chinese.java :
public abstract class Chinese implements Person{
public Chinese() {
System.out.println("Spring实例化主调Bean:Chinese实例...");
}
//singleton Bean里增加一个抽象方法
//方法的返回值类型是被依赖的Bean
public abstract Axe getAxe();
@Override
public void useAxe() {
System.out.println("正在使用"+getAxe()+"砍柴!");
System.out.println(getAxe().chop());
}
}
bean.xml核心配置:
<bean id="chinese" class="com.bean.Chinese">
<lookup-method name="getAxe" bean="steelAxe"/>
</bean>
<bean id="steelAxe" class="com.bean.SteelAxe" scope="prototype"/>
Test.java :
public class Test {
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("bean.xml");
Person p=(Person) ctx.getBean("chinese");
p.useAxe();
p.useAxe();
}
}
运行Test.java,控制台输出:
SteelAxe被部署成prototype作用域Bean,并被一个singleton作用域Bean所依赖。如果让Spring容器直接将prototype作用域的Bean注入singleton作用域Bean,就会出现不同步的问题。为了解决这个问题,我们在singleton Bean里新增一个抽象方法,该方法的返回值类型是被依赖的Bean,该方法是一个抽象方法,其实现由Spring完成。问题是:Spring怎么知道如何实现该方法呢?为了让Spring知道如何实现该方法,我们需要在配置文件中使用<lookup-method.../>元素来配置这个方法。
使用<lookup-method.../>元素需要指定如下两个属性:
① name:指定需要让Spring实现的方法。
② bean:指定Spring实现该方法后的返回值。
程序的执行结果表明:使用lookup方法注入后,系统每次调用getAxe( )方法都将生成一个新的SteelAxe实例,这就可以保证当singleton作用域的Bean需要全新的Bean实例时,直接调用getAxe( )方法即可,从而可以避免一直使用最早注入的Bean实例。