单例设计模式 vs Spring 容器中的单例 bean

112

众所周知,在Spring容器中,默认情况下我们拥有单例Bean,如果我们的Web应用程序基于Spring框架,则在这种情况下,我们是否真的需要实现Singleton设计模式来保存全局数据,而不仅仅是通过Spring创建一个Bean。

如果我没有能够解释清楚我的问题,请多包涵。

11个回答

73

在Spring中,单例模式和单例Bean是两个不同的概念。单例模式指的是对于每个类加载器只会创建一个特定类的实例。

Spring中单例Bean的范围被描述为“每个容器每个Bean”。它指的是每个Spring IoC容器中一个Bean定义对应一个对象实例。在Spring中,默认的范围是Singleton。

尽管默认的范围是Singleton,你可以通过指定<bean ../>元素的scope属性来改变Bean的范围。

<bean id=".." class=".." scope="prototype" />

13
每个容器每个Bean,意味着在Spring容器中只有一个类加载器。如果在Spring容器中有两个或更多个类加载器,则每个类加载器将拥有自己的实例。这是否意味着“每个容器每个类加载器每个Bean”?请澄清一下! - Dead Programmer
4
我认为这意味着Spring容器将使用一个它拥有的类加载器。你在Spring机制之外所做的不相关,也就是说,你可以创建自己的类加载器并创建任意多个类的实例,但如果通过Spring容器,它不会创建超过一个实例。 - inor
4
那么它们并不像你所说的“非常不同”。唯一的区别是范围——Spring容器和类加载器。 - Zack Macomber
1
什么是Spring IOC容器?默认情况下会创建多少个?如何以及为什么要增加其大小? - Satish Patro

44

我发现 "每个容器每个bean" 难以理解。我会说 "一个容器中每个bean ID 对应一个bean"。让我们通过一个例子来理解它。我们有一个叫做 Sample 的bean类。我在bean定义中定义了两个来自这个类的bean,例如:

<bean id="id1" class="com.example.Sample" scope="singleton">
        <property name="name" value="James Bond 001"/>    
</bean>    
<bean id="id7" class="com.example.Sample" scope="singleton">
        <property name="name" value="James Bond 007"/>    
</bean>

因此,每当我尝试获取具有ID“id1”的bean时,Spring容器将创建一个bean,将其缓存并在任何引用“id1”的地方返回相同的bean。如果我尝试使用id7获取它,则会从Sample类创建另一个bean,将其缓存,并在每次使用id7引用它时返回。

这与Singleton模式不太可能相同。在Singleton模式中,始终会为每个类加载器创建一个对象。但是在Spring中,将作用域设置为Singleton并不会限制容器从该类创建多个实例。它只是限制了对于相同ID的新对象创建,每当请求某个ID的对象时,都会返回先前创建的对象参考资料


2
讲解得很清楚。谢谢! - Swapnil
以这种方式表述完美而易于理解。老实说,我无法理解“每个容器每个豆子”...但在阅读了示例和提供的参考链接后,“每个容器一个豆子ID”更有意义。 谢谢,@Dexter。 - Aniket
1
非常好的解释!!答案涵盖了在单个应用程序上下文中从同一类创建的两个bean之间的区别。“GoF指定的单例设计模式意味着每个JVM一个bean。但是,在Spring中,它意味着每个应用程序上下文一个bean。按照GoF的定义,即使在同一JVM上运行多个应用程序上下文,也仍将只有一个单例类的实例。”因此,如果您可以补充您的答案,还包括多个应用程序上下文的其他情况,那就更加详尽了。 - kaushalpranav

31

在Spring中,单例范围指的是一个Spring上下文中的单个实例。Spring容器仅针对后续调用获取bean时返回相同的实例。


并且Spring并不关心bean的类是否编码为单例。实际上,如果该类编码为私有单例构造函数,Spring会使用BeanUtils.instantiateClass(javadoc 这里)将构造函数设置为可访问并调用它。

或者,我们可以在bean定义中使用factory-method属性,如下所示

    <bean id="exampleBean" class="example.Singleton"  factory-method="getInstance"/>

1
你确定需要factory-method属性吗?我很确定Spring知道如何获取实例,即使构造函数是私有的(可能尝试调用getInstance)。 - inor
这里有一个关于Spring如何调用私有构造函数的相关讨论:链接 - Xiawei Zhang

25

让我们来看一个最简单的例子:你有一个应用程序,你只使用默认的类加载器。你有一个类,由于某些原因,你决定它在应用程序中不应该有多个实例。(想象一下有几个人在应用程序的各个部分上工作的情况)。

如果您没有使用 Spring 框架,Singleton 模式确保您的应用程序中不会有多个类的实例。这是因为您无法通过执行“new”来实例化类的实例,因为构造函数是私有的。获取类的实例的唯一方法是调用类的某个静态方法(通常称为“getInstance”),该方法始终返回相同的实例。

如果您在应用程序中使用 Spring 框架,这意味着除了获取类的实例的常规方式(返回类的实例的 new 或静态方法)之外,您还可以要求 Spring 获取该类的实例,并且 Spring 将确保每当您请求该类的实例时,它都将始终返回相同的实例,即使您没有使用 Singleton 模式编写类。换句话说,即使该类具有公共构造函数,如果您总是向 Spring 要求该类的实例,Spring 在应用程序的生命周期内只调用那个构造函数一次。

通常,如果您正在使用 Spring,则应仅使用 Spring 创建实例,并且可以为类使用公共构造函数。但是,如果您的构造函数不是私有的,则无法真正防止任何人绕过 Spring 直接创建类的新实例。

如果您确实想要一个类的单个实例,即使在应用程序中使用 Spring 并将该类定义为单例,也唯一的方法是还要使用 Singleton 模式来实现该类。这确保了只有一个实例,无论人们是否使用 Spring 来获取实例或绕过 Spring。


12

Spring 中的单例范围意味着这个 bean 只会被 Spring 实例化一次。与原型范围(每次新实例)、请求范围(每个请求一次)和会话范围(每个 HTTP 会话一次)不同。

单例范围在技术上与单例设计模式无关。您不必将您的 bean 实现为单例,它们也可以被放置在单例范围中。


1
如果我理解正确的话,你表示如果我需要将任何对象实现为单例,则无需实现单例模式,使用Spring创建该bean即可。现在,针对Singleton设计模式和Spring框架中的Singleton范围,我的理解有些混乱。 - Peeyush
1
Spring不强制使用Singleton模式。 - lexicore
我想更新@lexicore关于作用域的概述。如果我错了,请纠正我,从Spring v4.3版本开始,有6个内置作用域,分别是Singleton、Prototype、Request、Session、Application和WebSocket。最后4个作用域仅在使用Web-aware ApplicationContext时可用,而Singleton和Prototype作用域在任何类型的IoC容器中都可用。这是Spring官方文档链接。这是另一个链接。谢谢。 - Aniket

5

这两者之间有非常根本的区别。在单例设计模式中,每个类加载器只会创建一个类的实例,而在Spring单例中,为给定ID创建共享bean实例的IoC容器不止一个。

例如,如果我有一个名为“SpringTest”的类,我的XML文件看起来像这样:

<bean id="test1" class="com.SpringTest" scope="singleton">
        --some properties here
</bean>    
<bean id="test2" class="com.SpringTest" scope="singleton">
        --some properties here   
</bean>

现在,在主类中,如果您检查上述两个引用的参考,根据Spring文档,它将返回false:

当一个bean是单例时,仅管理一个共享的bean实例,并且所有对于与该bean定义匹配的id或ids的bean的请求都会由Spring容器返回该特定的唯一的bean实例。

因此,在我们的情况下,虽然类相同但提供的ID不同,因此导致创建了两个不同的实例。


3
到目前为止,所有的答案都集中在解释设计模式和Spring单例之间的区别,并没有回答你的实际问题:应该使用Singleton设计模式还是Spring单例Bean?哪个更好?
在我回答之前,让我先说一下,你两者都可以做。你可以将bean实现为Singleton设计模式,并使用Spring将其注入到客户端类中作为Spring单例bean。
现在,问题的答案很简单:不要使用Singleton设计模式!而是使用Spring单例bean,它实现为一个带有public构造函数的类。为什么呢?因为Singleton设计模式被认为是一种反模式。主要是因为它使测试变得复杂。(如果不使用Spring来注入它,则所有使用Singleton的类现在都与它紧密绑定),而且你不能替换或扩展它。可以通过Google搜索“Singleton反模式”来获取更多信息,例如Singleton反模式 使用Spring单例是正确的选择(单例bean实现不是Singleton设计模式,而是带有public构造函数的类),这样Spring单例bean可以轻松地进行测试,使用它的类也不会与其紧密耦合,而是Spring将单例(作为接口)注入到需要它的所有bean中,而单例bean可以随时替换为另一种实现,而不会影响使用它的客户端类。

2

Spring中的单例Bean和基于单例设计模式的类是有很大区别的。

单例模式确保一个特定类的实例仅会在每个类加载器中创建一次,而Spring单例bean的范围被描述为“每个容器每个bean”。 在Spring中,单例作用域意味着该bean仅由Spring实例化一次。Spring容器仅返回相同的实例,以便在后续调用中获取该bean。


13
你是“Java 牛人”,对吗?这就使得你的话,“Found a good explanation and example at...”成为一个不诚实的试图隐藏你正在链接到自己的网站的言论。无论如何,你提供的链接似乎并不重要。我将其删除,以避免答案被认为是垃圾信息而被删除。在发布任何指向你的网站链接之前,请阅读自我推广方面的常见问题解答。同时请注意,把你的网站链接放在个人简介中也完全没问题。 - Andrew Barber

2

Spring单例bean被描述为“每个容器每个bean”。Spring中的单例作用域意味着相同的对象在相同的内存位置将返回给相同的bean id。如果创建同一类的多个不同id的bean,则容器将向不同的id返回不同的对象。这就像一个键值映射,其中键是bean id,值是一个Spring容器中的bean对象。 而单例模式确保每个类加载器只创建一个特定类的实例。


1
在Spring中,“singleton”使用bean工厂获取实例,然后将其缓存;这是严格的单例设计模式,实例只能从静态get方法中检索,并且对象永远不能公开实例化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接