Spring中的享元设计模式

2,460 阅读2分钟

设计模式里面有一种模式叫做享元模式。

①. 共享的对象创建一次即可,之后缓存起来,放入工厂,避免频繁对象创建,回收的的开销

②. 将变化的对象剥离开来,将大粒度的对象拆分成小粒度的对象,不用重复创建的可以不再重复创建,避免大量相似类的开销,从而提高系统资源的利用率。

我们以商品的秒杀活动为例,当客户下完订单后,变化的是商品的库存,而商品的其他属性仍然不变。 所以可以将商品活动类(Activity)缓存到商品工厂(ActivityFactory)里,每次库存改变时,new一个新的库存类Stock塞到商品活动类中即可。

示意图:

实际上spring中也有这样的类似的情况:

spring 中的 bean 默认的是单例的(spring中单例bean也是存在map中的),这样和享元模式一样已经将对象放入到工厂里面了,剩下的就是缓存对象中可变的属性了,到这里你可能会和我一样想到:将单例对象中的属性设置成原型不就行了?

但细想好像又不辣么对,Spring在实例化单例 bean的时候,只会实例化一次,相关属性注入也只会注入一次,之后就放进singletonObjects里面了,以后每次获取时都从singletonObjects里面获取了,其引用的原型对象应该也是同一个了,这就有点矛盾了。

百想不如一动,写代码试试呗。

定义spring.xml:

<bean id="myTestBean" class="org.springframework.beans.factory.test.bean.MyTestBean" scope="prototype">
   <property name="userId" value="1"></property>
   <property name="age" value="18"></property>
   <property name="userName" value="akun" ></property>
</bean>

<bean id="singletonReferencePrototype" class="org.springframework.beans.factory.test.bean.SingletonReferencePrototype">
   <property name="myTestBean" ref="myTestBean"></property>
</bean>

定义一个SingletonReferencePrototype 类,其中包含引用myTestBean,将myTestBean的scope 设置为prototype,OK,编写测试代码:

public static void main(String[] args) {
   DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
   XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
   beanDefinitionReader.loadBeanDefinitions(new ClassPathResource("spring.xml"));

   SingletonReferencePrototype referencePrototype = (SingletonReferencePrototype) beanFactory.getBean("singletonReferencePrototype");
   System.out.println(referencePrototype.getMyTestBean() == referencePrototype.getMyTestBean());

}

查看打印结果:

true

额,referencePrototype.getMyTestBean() == referencePrototype.getMyTestBean() 打印出来是true,这说明从缓存里面获取到的单例对象 所引用的对象并不是“prototype”的,这就有点违背我们的初衷了。

那我们应该怎样让获取到的引用对象 是 “prototype” 的呢?答案很简单,使用spring的方法注入(请注意这里是方法注入,不是setter方法注入,)。

上代码,为了区分我们使用SingletonReferencePrototype1

<bean id="myTestBean" class="org.springframework.beans.factory.test.bean.MyTestBean" scope="prototype">
   <property name="userId" value="1"></property>
   <property name="age" value="18"></property>
   <property name="userName" value="akun" ></property>
</bean>

<bean id="singletonReferencePrototype1" class="org.springframework.beans.factory.test.bean.SingletonReferencePrototype1">
   <lookup-method name="getMyTestBean" bean="myTestBean"></lookup-method>
</bean>

通过spring的 lookup-method 指定方法名是getMyTestBean(getter方法,在这里你高兴的话可以称之为gerter方法注入),引用的bean 是 myTestBean, 而此时myTestBean声明的scope依然是“prototype” 的。

再测试:

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.loadBeanDefinitions(new ClassPathResource("spring.xml"));

SingletonReferencePrototype1 referencePrototype = (SingletonReferencePrototype1) beanFactory.getBean("singletonReferencePrototype1");
System.out.println(referencePrototype.getMyTestBean() == referencePrototype.getMyTestBean());

false

这样就OK啦。

附录:

spring官网

docs.spring.io/spring-fram…

spring.io/blog/2004/0…

相关博客:

使用scoped-proxy解决单例依赖原型:

 juejin.cn/post/686789…

源码分析spring的scoped-proxy

juejin.cn/post/686940…