最近在项目开发过程中碰到了singleton bean中依赖prototype bean的问题,在官网和社区学习了一下,发现有一些使用方法是错误的,在这里自己测试了几个正确的使用方式,在这里供大家交流。
首先是示例代码,这是省略版的应用场景。
@Service(Scope="singleton")
public class SingletonServiceImpl implements SingletonService{
@Resource
PrototypeBean prototypeBean;//直接在单例模式中注入原型bean,prototypeBean其实只实例化了一次,原型模式的作用完全没有发挥
public void start(){
prototypeBean.setFoo(new Foo());
prototypeBean.setBar(new Bar());
process(prototypeBean);
}
public void process(PrototypeBean prototypeBean){
//......
}
}
@Service(Scope="prototype")
public class PrototypeBean{
private int count = 0;
private Foo foo;
private Bar bar;
public void setFoo(Foo foo){
this.foo = foo;
count = count+1;
System.out.println(count);
}
public void setBar(Bar bar){
this.bar = bar;
count = count+1;
System.out.println(count);
}
}
现在的目标是每次调用start方法,都会实例化一个新的prototypeBean,有如下几种方案:
1.放弃部分ioc能力,手动调用getBean方法(代码中会出现spring相关依赖代码,不推荐):
@Service(Scope="singleton")
public class SingletonServiceImpl implements SingletonService,ApplicationContextAware{
ApplicationContext applicationContext;
public void start(){
PrototypeBean prototypeBean = applicationContext.getBean("prototypeBean",PrototypeBean.class);
prototypeBean.setFoo(new Foo());//output 1
prototypeBean.setBar(new Bar());//output 2
process(prototypeBean);
}
public void process(PrototypeBean prototypeBean){
//......
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
2.lookup方法注入
该方法利用cglib动态生成一个SingletonServiceImpl的子类,该子类会自动override有lookup注解的方法,代码干净整洁。
@Service(Scope="singleton")
public class SingletonServiceImpl implements SingletonService{
public void start(){
PrototypeBean prototypeBean = this.createPrototypeBean();
prototypeBean.setFoo(new Foo());//output 1
prototypeBean.setBar(new Bar());//output 2
process(prototypeBean);
@Lookup
public PrototypeBean createPrototypeBean(){
return null;//该方法会被cglib自动重写,所以直接返回null就可以。注意方法不能是private,否则cglib不能继承该方法
}
public void process(PrototypeBean prototypeBean){
//......
}
3.使用动态代理
spring提供了动态代理,在注入PrototypeBean时会先注入一个PrototypeBean的代理类,等方法调用时,由代理类分配具体的实例,还是先看代码:
@Service(Scope="singleton")
public class SingletonServiceImpl implements SingletonService{
@Resource
PrototypeBean prototypeBean;
public void start(){
prototypeBean.setFoo(new Foo());
prototypeBean.setBar(new Bar());
process(prototypeBean);
}
public void process(PrototypeBean prototypeBean){
//......
}
}
//如果是接口,就使用ScopeProxyMode.INTERFACES
@Service(Scope="request",proxyMode=ScopeProxyMode.TARGET_CLASS)
public class PrototypeBean{
private int count = 0;
private Foo foo;
private Bar bar;
public void setFoo(Foo foo){
this.foo = foo;
count = count+1;
System.out.println(count);
}
public void setBar(Bar bar){
this.bar = bar;
count = count+1;
System.out.println(count);
}
}
重点来了,请注意PrototypeBean的注解中,scope我使用了request。为什么不使用prototype呢,因为在动态代理时,如果使用prototype,对prototypeBean中方法的每一次调用,都会生成一个新的prototypeBean实例,如果scope使用prototype,则会出现下面的情况:
public void start(){
prototypeBean.setFoo(new Foo());//output 1
prototypeBean.setBar(new Bar());//output 1此时的prototypeBean与上面一行的prototypeBean是两个实例,这样违背了我们希望在start中得到一个新实例的初衷。
process(prototypeBean);
}
由于我主要是参考了spring的官方文档和自己构造案例测试,并没有阅读源码。有不对之处欢迎大家指出~