Spring Bean 的作用域默认是单例 (Singleton),同时支持修改为多例 (Prototype)。那么怎么具体来实现呢?
Singleton
先来了解一下什么是单例。
我们举一个不太恰当的例子,我们在写一个配置类 ArrayListConfig,用来把 ArrayList 交给 IOC 容器管理,如下:
@Configuration
public class ArrayListConfig {
@Bean
public ArrayList<String> arrayList() {
return new ArrayList<String>();
}
}
- 注意,事实上,我们正常开发做项目的时候是不会让
IOC来管理ArrayList的生命周期的,这里仅仅为了方便演示,便于学习。
好,我们现在创建好了这个配置类,然后我们写一个单元测试:
@Autowired
private ArrayList<String> arrayList1;
@Autowired
private ArrayList<String> arrayList2;
@Test
void test1(){
arrayList1.add("1");
arrayList2.add("2");
System.out.println(arrayList1);
System.out.println(arrayList2);
}
那么大家思考一下,打印输出的结果会是什么呢?

他输出的都是 [1, 2],这就是 Singleton。在 IOC 容器启动时,就会创建一个 Bean 实例,之后我们自动装配后获得的对象,都是指向同一个 Bean 实例,因此才会输出同样的内容。
那么我们如何才能够实现这两个 ArrayList 指向不同的 Bean 实例呢?这就要求我们使用多例 Prototype 了。
Prototype
多例很简单,我们只需要使用注解 @Scope 即可,例如:
@Configuration
public class ArrayListConfig {
@Bean
@Scope("prototype")
public ArrayList<String> arrayList() {
return new ArrayList<String>();
}
}

如上图,此时便达到我们想要的效果了,这就是多例。
实际上,单例是 @Scope("singleton"),但我们一般省略不写。
@Scope 也可以结合 @Compoment 等在定义类时直接实现单例或多例,这里便不再演示。
- 注意,多例 Bean 有时候也称为原型 Bean。
Bean 的生命周期
不知道大家有没有思考过 Bean 的生命周期这一问题。
就比如,我们交给 IOC 容器管理的 Bean,他都是什么时候创建的呢?就比如我们上面的那个 ArrayList,他是在我们第一次使用的时候创建的,还是容器一启动的时候就创建好了?
为了便于测试,现在我先手动创建一个类,并交给 IOC 容其管理,代码如下:
@Component
public class Kunkun {
public Kunkun() {
System.out.println("坤坤诞生了!");
}
}
然后我们写个单元测试:
@Test
void test2(){
}

如上,我们发现,即使没有使用 Kunkun(我们也没有 @Autowired 一个 Kunkun),Kunkun 的构造函数也被调用了,这说明默认情况下,容器启动时初始化单例 Bean。
那么有些时候,提前把所有 Bean 对象都创建好是比较浪费资源的,那么我们能不能这样,就是我们第一次使用这个 Bean 的时候,再去创建,之后再用直接就从 IOC 容器中拿就可以了,能否这样呢?是可以的,我们只需要加上 @Lazy 这个注解就可以了,例如:
@Component
@Lazy
public class Kunkun {
public Kunkun() {
System.out.println("坤坤诞生了!");
}
}
这个时候我们再去运行刚才的单元测试 Test2 就不会有任何输出了。
那么此时我们修改一下单元测试,如下:
@Autowired
private Kunkun kunkun;
@Test
void test2(){
}
我们此时运行发现:

这说明,此时 Kunkun 的 Bean 对象在使用时,才进行了创建。
请注意,我们上述讲的都是针对单例 Bean 的情况,如果是多例 Bean 呢?
我们不妨在刚才的类里加一个 @Scope("prototype"),如下:
package top.mygld.demo.test;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype")
public class Kunkun {
public Kunkun() {
System.out.println("坤坤诞生了!");
}
}
此时我们仍然不声明并创建 Kunkun,来看看启动时有没有出现 Kunkun 的构造方法。
经过测试,发现的确没有任何关于 Kunkun 构造方法的输出,这说明对于多例 Bean,不会在 IOC 容器启动时就被初始化创建。
那么我们 @Autowired 两个 Kunkun,如下:
@Autowired
private Kunkun kunkun1;
@Autowired
private Kunkun kunkun2;
@Test
void test3(){
}
运行后输出:

可以看到,构造方法调用成功了,我们不难得出结论,对于多例 Bean,默认就是懒加载方式,如果你还想加 @Lazy,当然也是不会报错的,但是加上没什么用。
好了,聊完了单例和多例 Bean 的创建,我们再聊聊销毁。
先看单例 Bean 的销毁:
有一个注解 @PreDestroy,被它所注解的方法,在 Bean 销毁时会自动执行,例如:
@Component
public class Kunkun {
public Kunkun() {
System.out.println("坤坤诞生了!");
}
@PreDestroy
public void preDestroy() {
System.out.println("Kunkun 被销毁~");
}
}
我们随便写个单元测试,@Autowired 一个 Kunkun 看看效果:

可以发现,对于单例 Bean,在容器被销毁时,Bean 实例也会被销毁。
那么我们再把他变成多例 Bean 实验一下:
package top.mygld.demo.test;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
@Component
@Scope("prototype")
public class Kunkun {
public Kunkun() {
System.out.println("坤坤诞生了!");
}
@PreDestroy
public void preDestroy() {
System.out.println("Kunkun 被销毁~");
}
}
在单元测试中 @Autowired 两个 Kunkun,并测试:

我们发现,只有构造方法被调用了,但容器被销毁后,Bean 对象并没有被销毁。这是因为对于多例 Bean,容器并不会去管理它的销毁,必须手动强制去销毁,如下:
@Autowired
private Kunkun kunkun1;
@Autowired
private Kunkun kunkun2;
@Test
void test3(){
kunkun1.preDestroy();
kunkun2.preDestroy();
}
总结
- 默认作用域
- Spring Bean 默认是 单例(Singleton),整个容器共享一个实例。
- 可以通过
@Scope("prototype")或“多例(Prototype)”来每次获取都创建新实例。
- 创建时机
- 单例 Bean:容器启动时创建(非
@Lazy时) - 多例 Bean:每次获取时创建,默认就是按需创建(懒加载)
@Lazy:用于延迟单例 Bean 初始化,对多例 Bean 无实际意义
- 单例 Bean:容器启动时创建(非
- 销毁机制
- 单例 Bean:容器关闭时自动销毁,支持
@PreDestroy或DisposableBean.destroy() - 多例 Bean:容器不管理销毁,需要手动调用销毁方法
- 单例 Bean:容器关闭时自动销毁,支持
- 使用场景
- 单例:全局共享对象、服务类、配置类
- 多例:临时对象、短期使用对象、频繁创建的计算或任务对象
- 关键点
- 单例 Bean 生命周期长,容器全权管理
- 多例 Bean 生命周期短,开发者负责资源释放
@Lazy主要优化单例 Bean 的初始化,减少启动开销