单例bean依赖原型bean的 如何保证原型bean生命周期
简介:
将原型bean注入到单例bean,会破坏原型bean的生命周期,使其的生命周期变成与单例bean相同。
好了,废话不多少,直接上栗子,边吃边说。
情况模拟:
1、单例bean
@Component public class SingletonBean { @Autowired private PrototypeBean prototypeBean; public void printTime() { System.out.println("SingletonBean: "+this.hashCode()); System.out.println("prototypeBean 注入给单例的:"+prototypeBean.hashCode()); prototypeBean.printTime(); } }
2、原型bean
@Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class PrototypeBean { Long timeMilis; public PrototypeBean() { //System.out.println("PrototypeBean Constructor"); this.timeMilis = System.currentTimeMillis(); } public void printTime() { System.out.println("PrototypeBean原始bean:"+this.hashCode()); System.out.println("timeMils:" + timeMilis); } }
3、测试类
public class PrototypeTest { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext= new AnnotationConfigApplicationContext(PrototypeBeanConfig.class); SingletonBean singletonBean = (SingletonBean) applicationContext.getBean("singletonBean"); SingletonBean singletonBean1 = (SingletonBean) applicationContext.getBean("singletonBean"); SingletonBean singletonBean2 = (SingletonBean) applicationContext.getBean("singletonBean"); singletonBean.printTime(); singletonBean1.printTime(); singletonBean2.printTime(); } }
4、测试结果:
5、原因分析:
要知道, SingletonBean 是单例模式的,只会被Spring进行类扫面的时候创建一次,然后存入到单例池SingletonObjects。之后每次使用SingletonBean,都会从单例池中获取,不会再次创建。因为属性的注入发生在创建bean的过程中,所以也只会在创建的时候注入一次。也就是说,在SingletonBean创建过程中,将生成一个PrototypeBean类的bean作为属性注入到SingletonBean中。而以后使用SingletonBean 的 prototypeBean 属性时不会重新生成PrototypeBean类的bean,只会使用已经注入到SingletonBean属性上的bean。因此PrototypeBean失去了原型bean的生命周期。
解决方式:
一、单例bean实现ApplicationContextAware接口
单例bean不让spring进行属性原型bean的属性注入。而是每次使用的时候,通过getBean的方式获取原型bean。
1、单例bean
@Component public class SingletonBeanImplementApplicationContextAware implements ApplicationContextAware { ApplicationContext applicationContext; private PrototypeBean prototypeBean; public void printTime() { System.out.println("SingletonBean: " + this.hashCode()); prototypeBean= (PrototypeBean) applicationContext.getBean("prototypeBean"); System.out.println("prototypeBean 注入给单例的:"+ prototypeBean.hashCode()); prototypeBean.printTime(); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext=applicationContext; } }
2、原型bean
@Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class PrototypeBean { Long timeMilis; public PrototypeBean() { //System.out.println("PrototypeBean Constructor"); this.timeMilis = System.currentTimeMillis(); } public void printTime() { System.out.println("PrototypeBean原始bean:"+this.hashCode()); System.out.println("timeMils:" + timeMilis); } }
3、测试类
public class PrototypeTest { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext= new AnnotationConfigApplicationContext(PrototypeBeanConfig.class); SingletonBeanImplementApplicationContextAware singletonBean = (SingletonBeanImplementApplicationContextAware) applicationContext.getBean("singletonBeanImplementApplicationContextAware"); SingletonBeanImplementApplicationContextAware singletonBean1 = (SingletonBeanImplementApplicationContextAware) applicationContext.getBean("singletonBeanImplementApplicationContextAware"); SingletonBeanImplementApplicationContextAware singletonBean2 = (SingletonBeanImplementApplicationContextAware) applicationContext.getBean("singletonBeanImplementApplicationContextAware"); singletonBean.printTime(); singletonBean1.printTime(); singletonBean2.printTime(); } }
4、测试结果:
5、原因分析:
让单例bean实现ApplicationContextAware接口,重写setApplicationContext方法,获取到ApplicationContex,每次使用 prototypeBean的时候,都从ApplicationContext中通过getBean的方式来获取最新的原型bean,每次获取的原型bean都是Spring新创建的bean。因此可以保证原型bean的生命周期。
二、@Lookup
通过加有@Lookup的抽象方法来获取prototypeBean对象。
1、单例bean
@Component public abstract class SingletonBeanByLookup { private PrototypeBean prototypeBean; @Lookup public abstract PrototypeBean getPrototypeBean(); public void printTime() { System.out.println(this.hashCode()); prototypeBean= getPrototypeBean() ; System.out.println(prototypeBean.hashCode()); prototypeBean.printTime(); } }
2、原型bean
@Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class PrototypeBean { Long timeMilis; public PrototypeBean() { //System.out.println("PrototypeBean Constructor"); this.timeMilis = System.currentTimeMillis(); } public void printTime() { System.out.println("PrototypeBean原始bean:"+this.hashCode()); System.out.println("timeMils:" + timeMilis); } }
3、测试类
public class PrototypeTest { public static void main(String[] args) throws InterruptedException { AnnotationConfigApplicationContext applicationContext= new AnnotationConfigApplicationContext(PrototypeBeanConfig.class); SingletonBeanByLookup singletonBean = (SingletonBeanByLookup) applicationContext.getBean("singletonBeanByLookup"); SingletonBeanByLookup singletonBean1 = (SingletonBeanByLookup) applicationContext.getBean("singletonBeanByLookup"); SingletonBeanByLookup singletonBean2 = (SingletonBeanByLookup) applicationContext.getBean("singletonBeanByLookup"); singletonBean.printTime(); Thread.sleep(1000); singletonBean1.printTime(); Thread.sleep(1000); singletonBean2.printTime(); } }
4、测试结果:
5、原因分析:
系统会生成SingletonBeanByLookup的代理对象作为 SingletonBeanByLookup对象的bean,
系统的代理对象是应用了Srping应用了CGLIB(动态代理)类库。Spring在初始化容器的时候对配置@lookup的bean做了特殊处理,Spring会对bean指定的class做动态代理,代理@lookup标签中name属性所指定的方法,返回bean属性指定的bean实例对象。
每次我们调用@lookup方法时,其实是调用了CGLIB生成的动态代理类的方法。
就是进入org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy.LookupOverrideMethodInterceptor#intercept(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], org.springframework.cglib.proxy.MethodProxy) 方法中
在此拦截器方法中 通过getBean 获取对应的bean。
三、原型bean使用代理模式
在原型bean的@Scope 标签中 添加“proxyMode = ScopedProxyMode.TARGET_CLASS”,如: @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,proxyMode = ScopedProxyMode.TARGET_CLASS)
1、单例bean
@Component public class SingletonBean { @Autowired private PrototypeBean prototypeBean; public void printTime() { System.out.println("SingletonBean: "+this.hashCode()); System.out.println("prototypeBean 注入给单例的:"+prototypeBean.hashCode()); prototypeBean.printTime(); } }
2、原型bean
@Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,proxyMode = ScopedProxyMode.TARGET_CLASS) public class PrototypeBean { Long timeMilis; public PrototypeBean() { //System.out.println("PrototypeBean Constructor"); this.timeMilis = System.currentTimeMillis(); } public void printTime() { System.out.println("PrototypeBean原始bean:"+this.hashCode()); System.out.println("timeMils:" + timeMilis); } }
3、测试类
public class PrototypeTest { public static void main(String[] args) throws InterruptedException { AnnotationConfigApplicationContext applicationContext= new AnnotationConfigApplicationContext(PrototypeBeanConfig.class); SingletonBean singletonBean = (SingletonBean) applicationContext.getBean("singletonBean"); SingletonBean singletonBean1 = (SingletonBean) applicationContext.getBean("singletonBean"); SingletonBean singletonBean2 = (SingletonBean) applicationContext.getBean("singletonBean"); singletonBean.printTime(); Thread.sleep(1000); singletonBean1.printTime(); Thread.sleep(1000); singletonBean2.printTime(); } }
4、测试结果:
5、原因分析:
这种方法将prototypeBean类生成一个代理对象赋值给singletonBean,每次调用代理方法的时候,会通过getBean去获取一个原始的PrototypeBean。
*
* This will lead to the creation of a proxy. That proxy is created once and will be
* returned for each call to getBean. As soon as you invoke a method on the proxy it will,
* based on the scope, either create a new one or reuse an existing one.
* As you have specified the scope as prototype each method invocation will lead to a new object.