1、概览 {#1概览}
单例对象经常被开发人员使用,因为应用程序中的许多对象都需要重复使用一个单例。在 Spring 中,我们可以通过 使用 Spring 的单例 Bean 或自己实现单例设计模式 来创建单例对象。
在本教程中,我们将首先了解单例设计模式及其线程安全的实现。然后,我们将了解 Spring 中的 Singleton Bean Scope,并将 singleton Bean 与使用单例设计模式创建的对象进行比较。
最后,我们将介绍一些可行的最佳实践。
本文中的 "
Singleton
Bean",即"单例 bean"。
2、单例设计模式 {#2单例设计模式}
单例是 "GoF" (si人帮?)于 1994 年发布的最简单的设计模式之一。它被归类于创建模式,因为单例提供了一种只创建一个实例的方法。
2.1、模式定义 {#21模式定义}
单例模式是指由一个类负责创建对象,并确保只创建一个实例。我们经常使用单例来共享状态或减少创建多个对象的成本。
单例模式实现可以确保只创建一个实例:
- 通过实现单个私有构造函数来隐藏所有构造函数。
- 仅在实例不存在时创建实例,并将其存储在私有静态变量中。
- 使用公共静态 getter 方法访问该单例。
让我们看看几个使用单例对象的类的示例:
在上面的类图中,我们可以看到多个服务如何使用只创建一次的同一个单例。
2.2、懒加载 {#22懒加载}
单例模式实现通常使用懒加载来延迟实例创建(也称为"懒汉式"),直到第一次实际需要时才创建。为了确保延迟实例化,我们可以在首次调用静态 getter 方法时创建实例:
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 方法
}
在多线程应用中,延迟加载可能会导致并发问题。因此,我们还应用了双重检查锁,以防止不同线程创建多个实例。
3、Spring 中的 Singleton Bean {#3spring-中的-singleton-bean}
Spring 框架中的 bean 是在 Spring IoC 容器中创建、管理和销毁的对象。
3.1、Bean Scope {#31bean-scope}
通过 Spring Bean,我们可以使用反转控制 (IoC) 通过元数据将对象注入 Spring 容器。实际上,一个对象可以定义其依赖,而无需创建它们,并将这项工作委托给 IoC 容器。
最新版本的 Spring 框架定义了六种 scope:
singleton
prototype
request
session
application
websocket
Bean 的 scope 定义了它的生命周期和可见性。它还决定了如何创建 bean 的实际实例。例如,我们可能想创建一个全局实例,或者每次请求 bean 时都创建一个不同的实例。
3.2、Singleton Bean {#32singleton-bean}
我们可以使用配置类中的 @Bean
注解在 Spring 中声明 Bean。Spring 中的 singleton scope 为容器中的每个 Bean 标识创建一个 Bean:
@Configuration
public class SingletonBeanConfig {
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public SingletonBean singletonBean() {
return new SingletonBean();
}
}
Singleton 是 Spring 中定义的所有 Bean 的默认 scope。因此,即使我们不使用 @Scope
注解指定特定的 scope,我们仍然会得到一个单例 Bean。这里包含的 scope 仅供参考。它通常用于表达其他可用的 scope。
3.3、Bean 标识符(Identifier) {#33bean-标识符identifier}
与纯粹的单例设计模式不同,我们可以从同一个类中创建多个单例 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();
}
对具有匹配标识符的 Bean 的所有请求都将导致框架返回一个特定的 Bean 实例。当我们在方法上使用 @Bean
注解时,Spring 会将方法名称用作 Bean 标识符。
注入 Bean 时,如果容器中存在多个相同类型的 Bean,框架会抛出 NoUniqueBeanDefinitionException
:
@Autowired
private SingletonBeanConfig.SingletonBean bean; //抛出异常
在这种情况下,我们可以使用 @Qualifier
注解来指定要注入 Bean 的标识符:
@Autowired
@Qualifier("singletonBean")
private SingletonBeanConfig.SingletonBean beanOne;
@Autowired
@Qualifier("anotherSingletonBean")
private SingletonBeanConfig.SingletonBean beanThree;
另外,当存在多个相同类型的 Bean 时,还可以使用另一个注解 @Primary
来定义主 Bean(存在多个相同类型的 Bean 注入时,优先注入 @Primary
标识的 Bean)。
4、对比 {#4对比}
现在,让我们比较一下这两种方法,并总结出 Spring 的最佳实践。
4.1、单例反模式 {#41单例反模式}
有些人认为单例是一种反模式,因为它引入了应用程序级的全局状态。使用单例的任何其他对象都直接依赖于单例。这就造成了类和模块之间不必要的相互依赖。
单例模式还违反了单一责任原则。因为单例对象至少要对两件事负责:
- 确保只创建一个实例。
- 执行正常业务。
此外,在多线程环境中,单例需要特殊处理,以确保独立线程不会创建多个实例。它们还可能增加单元测试和 mock 的难度。由于许多 mock 框架依赖于继承,私有构造函数使得单例对象难以 mock。
4.2、推荐方法 {#42推荐方法}
使用 Spring 的单例 Bean 而不是实现单例设计模式,可以消除上述许多缺点。
Spring 框架在所有使用 Bean 的类中注入 Bean,但保留了替换或扩展 Bean 的灵活性。该框架通过保持对 bean 生命周期的控制来实现这一点。因此,以后可以用另一种方法替换它,而无需更改任何代码。
此外,Spring Bean 还让单元测试变得更加简单。Spring Bean 易于 mock,框架可以将其注入测试类。我们可以选择注入实际的 Bean 实现或它们的 mock。
我们应该注意的是,单例 bean 不会只创建一个类的实例,而是在容器中为每个 bean 标识符创建一个 bean。
5、总结 {#5总结}
在本文中,我们探讨了如何在 Spring 框架中创建单例。我们研究了单例设计模式的实现,以及如何使用 Spring 的 singleton Bean。
我们探索了如何通过懒加载和线程安全实现单例模式。然后,我们研究了 Spring 中的单例 Bean scope,并探索了如何实现和注入单例 Bean。我们还了解了单例 Bean 与使用单例设计模式创建的对象之间的区别。
最后,我们了解了在 Spring 中使用 singleton Bean 如何消除单例设计模式传统实现的一些缺点。
参考:https://www.baeldung.com/spring-boot-singleton-vs-beans