單例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.