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获取。