【Spring】IOC容器的Scope详解以及本质
【是什么?】SpringIOC中的BeanScope到底是什么?
搜遍全网关于BeanScope的解释,似乎全是在教你怎么使用以及谈及Bean Scope 似乎都提及到了singleton、prototype、request、session、application、websocket 这些类型,而大多都把这6大类混为一谈。
说Scope 前我们先聊聊IOC容器
- 针对IOC容器,而我理解的IOC容器本质是解决对象之间的依赖关系管理和对象生命周期管理的一个容器,因为在没有IOC容器的情况下开发程序,随着业务逐渐变得复杂,所对应的对象之间的关系也会越来越难管理。但是如果要想共享数据最简单的方式那就是保证这个数据只有一份,如何使得数据只有一份?回答:“单例”。单例是既然想要数据共享,而且使得数据安全,“单例”是一个再好不过的方式了。但是随之而来的就是"单例"模式带来的弊端:“多线程下数据不安全”。既然单例数据不安全,那每个线程管理自己的对象就可以了,这样线程之间不会共享数据,也就不会有数据安全性的问题。所以我们就要打破“单例模式”的束缚,让我们看看Spring的IOC是如何使用“原型模式”打破这种束缚的。
关于单例和原型官方也给出解释
Spring文档中解释了在Spring一共有6中Scope类型,但是其中4中是只有在使用Web环境中才可以使用,所以说明在Spring中默认的只存在两种基本的BeanScope,也就是基本的Scope类型:singleton(默认)和prototype
单例模式
单例模式只有一个单例 Bean 的共享实例被管理,所有对具有符合该Bean定义的ID的Bean的请求都会被Spring容器返回该特定的Bean实例。换句话说,定义为 singleton,Spring IoC容器就会为该Bean定义的对象创建一个确切的实例。这个单一的实例被存储在这种单体Bean的缓存中,所有后续的请求和对该命名Bean的引用都会返回缓存的对象。下面的图片显示了 singleton scope 是如何工作的。。

原型模式
原型模式非 singleton prototype scope 导致每次对该特定Bean的请求都会创建一个新的Bean实例。也就是说,该 bean 被注入到另一个 bean 中,或者你通过容器上的 getBean() 方法调用来请求它。作为一项规则,你应该对所有有状态的 bean 使用 prototype scope,对无状态的 bean 使用 singleton scope。

这个图片似乎也很直观了,表示为三个对象分别应用了按个不同的”accountDao“对象。但是阅读文档中,官方给出这样的解释:

什么?看文档不爽?只看文档“纸上谈兵”? 那我们这就翻出源码解释
我们都知道,在Spring但凡需要创建对象的时候,最终都会走到doGetBean这个方法,在下面这个方法做出了三个【mbd.isSingleton()、mbd.isPrototype()、自定义类型】类型判断,其中"mbd"是Bean的定义信息。如果定义信息里存在是原型模式的配置,则不会走默认getSingleton 这个方法,每次都会createBean都会去创建一个新对象。
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
String beanName = this.transformedBeanName(name);
Object sharedInstance = this.getSingleton(beanName);
.................
if (mbd.isSingleton()) {
sharedInstance = this.getSingleton(beanName, () -> {
try {
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
});
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
//判断了是否设置了原型模式
} else if (mbd.isPrototype()) {
var11 = null;
Object prototypeInstance;
try {
this.beforePrototypeCreation(beanName);
//进入了原型模式的对象创建
prototypeInstance = this.createBean(beanName, mbd, args);
} finally {
this.afterPrototypeCreation(beanName);
}
bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
...................
总结
这也就意味Spring所实现的IOC容器并不去管理原型模式对象,IOC也没办法去管理原型对象,这也可能是由于历史问题:Spring的原型模式可以选择是浅拷贝(Shallow Copy)还是深拷贝(Deep Copy)。具体选择哪种拷贝方式,取决于实现方式。默认情况下,Spring的原型模式使用的是浅拷贝(什么是浅拷贝?需要补充下知识点这里不去展开详解大致浅拷贝只复制对象本身和对象内的基本数据类型,而不会复制对象内的引用类型变量。)这意味着,如果对象内有引用类型的变量,那么新对象和原对象将共享这些引用类型的变量。要实现深拷贝,需要自定义拷贝方式,例如实现Cloneable接口并重写clone方法,或者使用序列化和反序列化等其他方法。在Spring的原型模式中,也可以选择使用深拷贝,但需要额外配置。完全实现深拷贝可能是非常复杂的,并且可能会涉及到对象内的层层递归引用。因此,在大多数情况下SpirngIOC只是为了”解耦“,所以浅拷贝更为常见和实用。
【怎么用?】说了那么多“原型模式”到底有什么用,实际工作业务场景又有什么用?
- 在这里我举出一个实际的场景:
- 业务场景:EasyExcel中我们读取表格进行导入的时候可能需要设置不同类型Service进行数据校验或者别的
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class DemoDataListener extends AnalysisEventListener<DemoData> {
/**
* 表格中的数据
*/
private List<DemoData> dataList = new ArrayList<>();
/**
* 批次
*/
private Integer count = 100;
@Autowired
private DemoService demoService;
@Override
public void invoke(DemoData data, AnalysisContext context) {
dataList.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
demoService.save(dataList);
}
}
由于每次达到一定批次才可以导入,但又由于我们吧#Class.DemoDataListener 这个类型托管给Spring容器,因为我们想在其中使用含有状态的业务Service服务类,但是从而导致的问题就是dataList 在单例模式下多线程会被共享,倒是List数据出现问题,所以采用原型模式来解决了此问题。
本文详细解读了SpringIoC容器中的BeanScope概念,重点介绍了单例和原型模式,并通过实际业务场景说明了原型模式在处理有状态对象时的优势。
3360





