Skip to content

Spring Bean 的作用域与生命周期

发表于: 时间 23:31
加载中...

Spring Bean 的作用域默认是单例 (Singleton),同时支持修改为多例 (Prototype)。那么怎么具体来实现呢?

Singleton

先来了解一下什么是单例。

我们举一个不太恰当的例子,我们在写一个配置类 ArrayListConfig,用来把 ArrayList 交给 IOC 容器管理,如下:

@Configuration
public class ArrayListConfig {

    @Bean
    public ArrayList<String> arrayList() {
        return new ArrayList<String>();
    }

}

好,我们现在创建好了这个配置类,然后我们写一个单元测试:

@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);
}

那么大家思考一下,打印输出的结果会是什么呢?

image.png

他输出的都是 [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>();
    }

}

image.png

如上图,此时便达到我们想要的效果了,这就是多例。

实际上,单例是 @Scope("singleton"),但我们一般省略不写。

@Scope 也可以结合 @Compoment 等在定义类时直接实现单例或多例,这里便不再演示。

Bean 的生命周期

不知道大家有没有思考过 Bean 的生命周期这一问题。

就比如,我们交给 IOC 容器管理的 Bean,他都是什么时候创建的呢?就比如我们上面的那个 ArrayList,他是在我们第一次使用的时候创建的,还是容器一启动的时候就创建好了?

为了便于测试,现在我先手动创建一个类,并交给 IOC 容其管理,代码如下:

@Component
public class Kunkun {
    public Kunkun() {
        System.out.println("坤坤诞生了!");
    }
}

然后我们写个单元测试:

@Test
void test2(){
    
}

image.png

如上,我们发现,即使没有使用 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(){
    
}

我们此时运行发现:

image.png

这说明,此时 KunkunBean 对象在使用时,才进行了创建。

请注意,我们上述讲的都是针对单例 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(){

}

运行后输出:

image.png

可以看到,构造方法调用成功了,我们不难得出结论,对于多例 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 看看效果:

image.png

可以发现,对于单例 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,并测试:

image.png

我们发现,只有构造方法被调用了,但容器被销毁后,Bean 对象并没有被销毁。这是因为对于多例 Bean,容器并不会去管理它的销毁,必须手动强制去销毁,如下:

@Autowired
private Kunkun kunkun1;
@Autowired
private Kunkun kunkun2;
@Test
void test3(){
    kunkun1.preDestroy();
    kunkun2.preDestroy();
}

总结

  1. 默认作用域
    • Spring Bean 默认是 单例(Singleton),整个容器共享一个实例。
    • 可以通过 @Scope("prototype") 或“多例(Prototype)”来每次获取都创建新实例。
  2. 创建时机
    • 单例 Bean:容器启动时创建(非 @Lazy 时)
    • 多例 Bean:每次获取时创建,默认就是按需创建(懒加载)
    • @Lazy:用于延迟单例 Bean 初始化,对多例 Bean 无实际意义
  3. 销毁机制
    • 单例 Bean:容器关闭时自动销毁,支持 @PreDestroyDisposableBean.destroy()
    • 多例 Bean:容器不管理销毁,需要手动调用销毁方法
  4. 使用场景
    • 单例:全局共享对象、服务类、配置类
    • 多例:临时对象、短期使用对象、频繁创建的计算或任务对象
  5. 关键点
    • 单例 Bean 生命周期长,容器全权管理
    • 多例 Bean 生命周期短,开发者负责资源释放
    • @Lazy 主要优化单例 Bean 的初始化,减少启动开销

上一篇文章
略微探讨 Spring 事务
下一篇文章
第三方 Bean 注入 IoC 容器