Spring中单例对象引用原型对象
在spring中,基本上所有的业务对象都是托管到了IOC中进行管理。
日常开发中我们会接触到的对象都大多数是单例的。当然,Spring中也提供了@Scope
注解让使用者可以对类的作用域进行声明。
但若某时候,若想要使用一个业务对象A引用一个业务对象B,而A是单例对象,但是B是应该是原型的,那么此时直接在原型类B上直接利用@Scope("prototype")
设置原型作用域后注入到A中是行不通的。
类A:
@Component
public class RefPrototype {
@Autowired
private ProDao proDao;
public void refMethod () {
proDao.daoMethod();
System.out.println("ref method");
}
}
类B:
@Repository
@Scope("prototype")
public class ProDao {
public void daoMethod() {
System.out.println(this.hashCode());
}
}
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(Config.class);
RefPrototype bean = ctx.getBean(RefPrototype.class);
bean.refMethod();
RefPrototype bean1 = ctx.getBean(RefPrototype.class);
bean1.refMethod();
RefPrototype bean2 = ctx.getBean(RefPrototype.class);
bean2.refMethod();
打印结果:
1397616978
ref method
1397616978
ref method
1397616978
ref method
这样打印出来的类B的hashcode其实都是一样的,也就是说,实际上注入了相同的类B。
其实原因还是挺简单的,因为你的单例类A在IOC中只实例化了一次,那么它对应的属性,在你没有手动去干预的情况下,它也是不会变动的。
想要实现这种效果,Spring官方文档里提供的第一种解决方案是实现ApplicationContextAware
接口,然后拿到IOC容器,对于这个多例类不采用DI的方式获取,而是每次通过IOC容器去手动获取。这就验证了我们前面所说的,手动干预。
ApplicationContextAware 接口
public interface ApplicationContextAware
extends Aware {
void setApplicationContext(ApplicationContext var1)
throws BeansException;
}
修改后的类A:
@Component
public class RefPrototype implements ApplicationContextAware {
// @Autowired
private ProDao proDao;
private ApplicationContext ctx;
public void refMethod () {
this.createProDao().daoMethod();
System.out.println("ref method");
}
public void setApplicationContext(ApplicationContext
applicationContext) throws BeansException {
this.ctx = applicationContext;
}
public ProDao createProDao () {
return this.ctx.getBean(ProDao.class);
}
}
setApplicationContext()
会在 ApplicationContextAwareProcessor
的invokeAwareInterfaces()
中调用并且将IOC设置进去。
这种方式是可以实现我们想要的那种效果的,放弃部分IOC。但是它的弊端就是业务代码直接耦合了框架,对框架的侵入性较高,这样是不利于代码的整洁和后期维护。
通过DL方式实现
除了上述方式外,spring文档也介绍了另外 一种方式,利用依赖查找的方式来实现单例类引用多例的问题。
此种方式要求对于需要多例出现的属性需要对应的有一个用于生产该类对象的抽象方法,方法需要用@LookUp("bean名字")
进行声明。但注意此类型的方法需要出现在容器负责实例化对象的代码位置上。而不能出现在例如用@Bean
修饰的方法上,因为此时是我们开发者在手动处理new的工作。
以下摘自原文档
The Spring Framework implements this method injection by using
bytecode generation from the CGLIB library to dynamically generate a
subclass that overrides the method.
此种方法的实质是利用cglib对我们声明的抽象查找方法进行了实现,进而我们才能使用它来获取我们需要的对象。
改造后的类A:
@Component
public abstract class RefPrototypeByLookUp {
public void refMethod () {
this.createProDao().daoMethod();
System.out.println("ref method");
}
@Lookup(value = "proDao")
public abstract ProDao createProDao();
}
测试代码:
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(Config.class);
RefPrototypeByLookUp bean =
=ctx.getBean(RefPrototypeByLookUp.class);
bean.refMethod();
RefPrototypeByLookUp bean1
= ctx.getBean(RefPrototypeByLookUp.class);
bean1.refMethod();
RefPrototypeByLookUp bean2 =
ctx.getBean(RefPrototypeByLookUp.class);
bean2.refMethod();
测试结果:
1173643169
ref method
1282287470
ref method
1397616978
ref method
测试可以看出,每个类B对象的hashcode都是不一样的,说明我们此时引用的并不是同一个对象,也达到了依赖原型类的目的。
这里的抽象方法也可以为实现方法,但是你可能没法拿到你想返回的对象,如果直接new的话是非常不推崇的,所以这里可以考虑直接返回一个null。spring会对该查找方法进行重写。
最后附上一段官方文档说明。
If the method is abstract, the dynamically-generated subclass
implements the method. Otherwise, the dynamically-generated subclass
overrides the concrete method defined in the original class.