解决Spring中singleton的Bean依赖于prototype的Bean的问题

    当Spring容器中作用域不同的Bean相互依赖时,可能出现一些问题,例如:一个作用域为Singleton的Bean(设为A)依赖于一个作用域为prototype的Bean(设为B)。由于A是单例的,只有一次初始化的机会,它的依赖关系也只在初始化阶段被设置,但它所依赖的B每次都会创建一个全新的实例,这将使A中的B不能及时得到更新。这样将导致如果客户端多次请求A,并调用A中B的某个方法(或获取A中B的某个属性),服务端总是返回同一个B,但客户端直接请求B却能获得最新的对象,这就产生了对象不同步的情况。这样就违背了B初衷:本来希望B具有prototype的行为,但是却表现出singleton的行为了。那么,问题如何解决呢?

    办法有二:

  • 部分放弃依赖注入:当A每次需要B时,主动向容器请求新的Bean实例,即可保证每次注入的B都是最新的实例。

  • 利用方法注入。

    第一种方式显然不是一个好的做法,代码主动请求Bean实例,必然导致代码与SpringAPI耦合在一起,造成严重的代码污染。通常情况下,我们会采用第二种做法。使用方法注入。

    方法注入通常使用lookup方法注入,利用lookup方法注入可以让Spring容器重写容器中Bean的抽象或具体方法,返回查找容器中其他Bean的结果,被查找的Bean通常是一个non-singleton的Bean(尽管也可以是一个singleton的Bean)。Spring通过使用CGLIB库修改客户端的二进制码,从而实现上述要求。看下面的例子:

public class CellPhone implements Phone {
    public CellPhone() {
        System.out.println("Spring实例化依赖的Bean...CellPhone实例");
    }
    public return call() {
        return "正在打电话...";
    }
}

    上面的CellPhone将被部署成prototype的Bean,并被一个singleton的Bean所依赖。如果让Spring容器直接将prototype的Bean注入到singleton中,就会出现上面的问题。为了解决这个问题,我们在singleton的Bean里增加一个抽象方法,该方法的返回类型是一个被依赖的Bean——注意这个方法是一个抽象方法,因为程序中没有为该方法提供实现,这个实现过程由Spring完成。下面是该singleton作用域的Bean的代码:

public abstract class Developer implements Person {
    public Developer() {
        System.out.println("Spring实例化主调的Bean...Developer实例");
    }
    //定义一个抽象方法,该方法将由Spring实现
    public abstract Phone getPhone();
    @Override
    public void call() {
        System.out.println("正在使用 " + getPhone() + " 打电话");
        System.out.println(getPhone().call());
    }
}

    上面的代码定义了一个抽象的getPhone方法,通常情况下,程序不能调用这个方法,但Spring框架将会负责为该方法提供是先提,这样这个方法就会变成具体方法了,程序也就可以调用该方法了。为了让Spring知道如何实现该方法,我们需要在配置文件中使用<lookup-method>标签,这个标签需要指定如下两个属性:

  • name:指定需要让Spring实现的方法

  • bean:指定Spring实现该方法后返回的值

    下面是配置片段:

<!-- 将CellPhone部署成prototype的范围 -->
<bean id="cellPhone" class="com.abc.CellPhone" scope="prototype" />
<bean id="developer" class="com.abc.Developer">
    <!-- getPhone方法返回CellPhone,每次调用将获取新的CellPhone -->
    <lookup-method name="getPhone" bean="cellPhone" />
</bean>

    上面配置的<lookup-method>指定Spring将负责实现getPhone方法,该方法将返回容器中的prototype类型的cellPhone实例。下面是测试类:

public class Test {
    public static void main(String args[]) {
        ApplicationContext context = 
                new ClassPathXmlApplicationContext("applicationContext.xml");
        Developer d = context.getBean("developer", Developer.class);
        d.call();
        d.call();
    }
}

    执行结果如下:

Spring实例化主调的Bean...Developer实例
Spring实例化依赖的Bean...CellPhone实例
正在使用 com.abc.CellPhone@3e12ad 打电话
Spring实例化依赖的Bean...CellPhone实例
正在打电话...
Spring实例化依赖的Bean...CellPhone实例
正在使用 com.abc.CellPhone@41af2e 打电话
Spring实例化依赖的Bean...CellPhone实例
正在打电话...

    结果表明:当lookup方法注入后,系统每次调用getPhone都会返回最新的CellPhone实例而非最早的CellPhone实例。

    注意:要保证lookup方法注入每次产生的Bean实例,必须将目标Bean(本例为cellPhone)布署成prototype作用域。否则,如果容器中只有一个目标Bean实例,即使采用lookup方法注入,每次依然返回同一个Bean实例。另外,lookup方法注入不仅能用于设值注入,还能用于构造注入。

转载于:https://my.oschina.net/itblog/blog/205860

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值