1. 概述

单例对象是开发中常用的模式,用于创建全局唯一的实例供多个组件共享。在Spring中,我们可以通过两种方式实现单例:使用Spring的单例Bean或手动实现单例设计模式

本文将首先介绍单例设计模式及其线程安全实现,然后探讨Spring中的单例Bean作用域,并对比两者差异。最后给出最佳实践建议。

2. 单例设计模式

单例模式是GoF在1994年提出的经典创建型模式,核心目标是确保一个类只有一个实例

2.1. 模式定义

单例模式要求一个类负责创建自身实例,并确保全局唯一。常用于共享状态或避免重复初始化开销。

实现单例的关键点:

  • 通过私有构造函数阻止外部实例化
  • 使用私有静态变量存储唯一实例
  • 提供公共静态方法获取实例

类图展示了多个服务共享同一单例实例: 单例设计模式类图

2.2. 延迟初始化

单例模式通常采用延迟初始化,在首次使用时创建实例。线程安全实现需使用双重检查锁定:

public final class ThreadSafeSingleInstance {

    private static volatile ThreadSafeSingleInstance instance = null;

    private ThreadSafeSingleInstance() {}

    public static ThreadSafeSingleInstance getInstance() {
        if (instance == null) {
            synchronized(ThreadSafeSingleInstance.class) {
                if (instance == null) {
                    instance = new ThreadSafeSingleInstance();
                }
            }
        }
        return instance;
    }

    //标准getter方法
}

⚠️ 多线程环境下必须使用volatile和双重检查锁定,否则可能创建多个实例。

3. Spring中的单例Bean

Spring中的Bean是由IoC容器创建、管理和销毁的对象

3.1. Bean作用域

通过控制反转(IoC),对象只需声明依赖而无需创建实例。Spring框架定义了六种作用域:

  • singleton(单例)
  • prototype(原型)
  • request(请求)
  • session(会话)
  • application(应用)
  • websocket(WebSocket)

作用域决定了Bean的生命周期和可见性。

3.2. 单例Bean

使用@Bean注解声明单例Bean:

@Configuration
public class SingletonBeanConfig {

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
    public SingletonBean singletonBean() {
        return new SingletonBean();
    }

}

✅ 单例是Spring的默认作用域,即使省略@Scope注解也会创建单例Bean。

3.3. Bean标识符

与纯单例模式不同,Spring允许同一类创建多个单例Bean

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public SingletonBean singletonBean() {
    return new SingletonBean();
}

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public SingletonBean anotherSingletonBean() {
    return new SingletonBean();
}

注入时需使用@Qualifier指定Bean标识符:

@Autowired
@Qualifier("singletonBean")
private SingletonBeanConfig.SingletonBean beanOne;

@Autowired
@Qualifier("anotherSingletonBean")
private SingletonBeanConfig.SingletonBean beanThree;

❌ 直接按类型注入会抛出NoUniqueBeanDefinitionException异常:

@Autowired
private SingletonBeanConfig.SingletonBean bean; //抛出异常

4. 对比分析

4.1. 单例模式的弊端

单例模式常被视为反模式,主要问题包括:

  • 引入全局状态,增加组件间耦合
  • 违反单一职责原则(同时负责实例控制和业务逻辑)
  • 多线程环境需特殊处理
  • 私有构造函数导致单元测试困难

4.2. 推荐方案

使用Spring单例Bean可避免上述问题:

  • 依赖注入降低耦合:Spring管理Bean生命周期,便于替换或扩展
  • 简化测试:轻松注入Mock对象
  • 容器级单例:每个Bean标识符对应一个实例,而非类级别单例

✅ Spring单例Bean在保持单例优势的同时,提供了更好的灵活性和可测试性。

5. 总结

本文对比了单例设计模式和Spring单例Bean的实现方式。我们探讨了线程安全的单例模式实现,分析了Spring单例Bean的作用域和注入机制,并指出Spring单例Bean在解耦、测试和灵活性方面的优势。

在Spring应用中,优先使用容器管理的单例Bean,避免手动实现单例模式,能获得更好的可维护性和扩展性。

完整源代码可在GitHub获取。


原始标题:Singleton Design Pattern vs Singleton Beans in Spring Boot | Baeldung