在此之前的文章都讲的是单例的实例化。本节讲的是多例的实例化。
什么是多例?
多例就是每次 getBean 操作得到的不是同一个对象。
我们在一个类上打上 @Component 注解后,还可以再打上一个 @Scope(“prototype”),使这个类在实例化的时候是多例的。
Scope 即作用域,除了单例和多例以外,还有其它的模式。它们最大的区别是,如何管理实例对象。单例是在完成工厂初始化的时候就将所有的 Bean 实例放进了缓存,使我们拿到的都是相同的实例,省去了反复创建和销毁的麻烦。而多例在工厂初始化的时候并没有创建,也没有放实例的缓存,所以我们每次拿到的是不同的对象。
以下是 doGetBean 方法中的一段:
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
//1.4.4.1
beforePrototypeCreation(beanName);
//这个方法和单例的是一样的,见1.4.2
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
//1.4.4.2
afterPrototypeCreation(beanName);
}
//该方法是FactoryBean接口的调用入口
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
完整的代码见Spring5 源码阅读笔记(1.4)finishBeanFactoryInitialization(beanFactory) 完成Bean工厂初始化
在这那一节中,我们知道如果是非抽象的且是单例的且非懒加载的 Bean,Spring 会在完成Bean工厂初始化这个方法里,将它初始化。但多例的并不会。所以在后来的 doGetBean 方法里我们才看到跟多例有关的代码。
这说明了只有当我们手动 getBean 才会进行多例的实例化。可以理解为多例是“懒加载”的。
跟源码
1.4.4.1 beforePrototypeCreation
类 AbstractBeanFactory
protected void beforePrototypeCreation(String beanName) {
//先从表示正在创建的容器里拿,这里的容器是一个ThreadLocal
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null) {
//如果容器里原本没有,直接放入
this.prototypesCurrentlyInCreation.set(beanName);
}
//如果容器里已经有一个beanName了,由于ThreadLocal只能存放一个值,
//就把原来的和要新添加的打包成一个set放入容器
else if (curVal instanceof String) {
Set<String> beanNameSet = new HashSet<>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
}
else {
//如果容器里本来就是一个set,就把变量放入set
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.add(beanName);
}
}
跟 prototypesCurrentlyInCreation:
使用 ThreadLocal 可以让多个线程彼此创建实例不受干扰。
1.4.4.2 afterPrototypeCreation
类 AbstractBeanFactory
protected void afterPrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal instanceof String) {
//从表示正在创建的容器移除
this.prototypesCurrentlyInCreation.remove();
}
else if (curVal instanceof Set) {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.remove(beanName);
if (beanNameSet.isEmpty()) {
this.prototypesCurrentlyInCreation.remove();
}
}
}