问题
在spring中,当一个单例Bean依赖一个原型Bean,原型Bean只会被注入一次,因此,原型Bean的作用域便会失效,与代码预期结果不同,便会造成问题。
<beanid="singletonObject"class="com.example.demo.application.data.SingletonObject"scope="singleton"><propertyname="prototypeObject"ref="prototypeObject"/></bean><beanid="prototypeObject"class="com.example.demo.application.data.PrototypeObject"scope="prototype"/>
多次获取prototypeObject得到的结果均为同一个Bean
解决
方法一:实现org.springframework.context.ApplicationContextAware接口,在每次使用prototypeObject时,重新调用getBean方法。
publicclassSingletonObjectimplementsApplicationContextAware{privatePrototypeObjectprototypeObject;privateApplicationContextcontext;//getter,setterandconstructorpublicPrototypeObjectgetPrototypeObject(){PrototypeObjectprototypeObject=context.getBean("prototypeObject",PrototypeObject.class);this.prototypeObject=prototypeObject;returnthis.prototypeObject;}@OverridepublicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{context=applicationContext;}}publicclassPrototypeObject{publicvoidcheck(){System.out.println(this.hashCode());}@OverridepublicinthashCode(){returnsuper.hashCode();}}
spring官网对该方式做出如下评价: The preceding is not desirable, because the business code is aware of and coupled to the Spring Framework. Method Injection, a somewhat advanced feature of the Spring IoC container, lets you handle this use case cleanly. 前面的方法是不可取的,因为业务代码获取并耦合Spring框架。method inject是Spring IoC容器的一个有点高级的特性,可以让你方便地处理这种情况。
方法二:Lookup-Method 标签
<beanid="singletonObject"class="com.example.demo.application.data.SingletonObject"scope="singleton"><lookup-methodname="createPrototypeObject"bean="prototypeObject"/></bean><beanid="prototypeObject"class="com.example.demo.application.data.PrototypeObject"scope="prototype"/>
publicabstractclassSingletonObject{privatePrototypeObjectprototypeObject;//getter、setterandconstructorpublicabstractPrototypeObjectcreatePrototypeObject();}publicclassPrototypeObject{publicvoidcheck(){System.out.println(this.hashCode());}@OverridepublicinthashCode(){returnsuper.hashCode();}}
PrototypeObjectprototypeObject1=singletonObject.createPrototypeObject();PrototypeObjectprototypeObject2=singletonObject.createPrototypeObject();System.out.println("prototypeObject1与prototypeObject2地址相同吗?"+(prototypeObject1==prototypeObject2));
返回结果为false,与预期相符合。
方法三:aop:scoped-proxy标签
```<beanid="singletonObject"class="com.example.demo.application.data.SingletonObject"scope="singleton"><propertyname="prototypeObject"ref="prototypeObject"/></bean><beanid="prototypeObject"class="com.example.demo.application.data.PrototypeObject"scope="prototype"><aop:scoped-proxy/></bean>
publicclassSingletonObject{privatePrototypeObjectprototypeObject;publicPrototypeObjectgetPrototypeObject(){returnprototypeObject;}publicvoidsetPrototypeObject(PrototypeObjectprototypeObject){this.prototypeObject=prototypeObject;}}publicclassPrototypeObject{publicvoidcheck(){System.out.println(this.hashCode());}@OverridepublicinthashCode(){returnsuper.hashCode();}}
GenericApplicationContextcontext=newGenericApplicationContext();newXmlBeanDefinitionReader(context).loadBeanDefinitions("classpath:/spring.xml");context.refresh();SingletonObjectsingletonObject=context.getBean("singletonObject",SingletonObject.class);PrototypeObjectprototypeObject=singletonObject.getPrototypeObject();prototypeObject.check();prototypeObject.check();
两次生成的hashcode分别为597190999、603443293,两个对象明显不同。符合预期。
思考
方法二和方法三其实都是通过代理模式实现的。代理模式无论是静态代理还是动态代理的JDK代理还是CGLIB代理都是通过组合的形式(被代理对象被当作代理对象的一个成员属性)完成的。 假设:A单例对象依赖B原型对象 则:方法二是通过代理A对象(也有可能是重写,A对象的lookup方法是否已经实现,spring都可以完成该功能,具体可参考sprnig官方文档) 方法三则是通过代理B对象实现的。当调用B对象的方法时,B的代理对象可以通过ApplicationContext重新生成B对象。 实际上方法三可以处理更多的应用场景,详细可以参考官方文档。