Spring的默认作用域是单例还是非单例?

58

能否解释一下为什么在下面展示的bean配置中,Spring会创建两个对象,因为默认情况下Spring的作用域是单例?

Spring的配置如下:

<bean id="customer" class="jp.ne.goo.beans.Customer"> 
    <property name="custno" value="100"></property>
    <property name="custName" value="rajasekhar"> </property>
</bean>
<bean id="customer2" class="jp.ne.goo.beans.Customer"> 
    <property name="custno" value="200"></property> 
    <property name="custName" value="siva"></property> 
</bean>

1
你在哪里?你还没有发布配置。 - smoggers
6
如果你定义了同一个类的两个豆子实例,那么就会有两个实例... - Gab
1
感谢Gab,因为我不明白单例的用途是什么?单例的意思是整个应用程序只应该创建一个实例(对象)。那么为什么Spring会为此创建两个对象呢? - Raj
23
你正在定义两个单例Bean,它们恰好具有相同的类型。单例是按bean而不是按类计算的。 - Jesper
9个回答

86

Spring的默认作用域是单例。只是你对什么是单例的理解与Spring定义的不一样。

如果你告诉Spring使用不同的id创建两个相同类的bean,那么你会得到两个独立的bean,每个bean都有单例作用域。单例作用域意味着当你引用具有相同id的内容时,你会得到相同的bean实例。

这里是Spring文档如何定义单例作用域

只有一个共享的单例bean实例被管理,所有请求具有与该bean定义匹配的id或ids的bean都会导致Spring容器返回该特定的bean实例。

单例作用域意味着使用相同的id检索相同的bean,仅此而已。测试没有两个id引用了相同的类将妨碍使用映射作为bean,并且会因通过BeanFactories代理事物而变得复杂。

Spring信任用户知道自己在做什么,而不是花费大量工作来监管这一点。

如果您想要一个bean的单例性质在多个名称之间保持不变,那是可以做到的。您可以使用alias让多个名称引用同一个bean:
在bean定义本身中,您可以通过使用id属性指定的一个名称和name属性中的任意数量的其他名称的组合为bean提供更多名称。这些名称可以是等效别名,指向同一个bean,并且对于某些情况非常有用,例如允许应用程序中的每个组件使用特定于该组件本身的bean名称来引用公共依赖项。
然而,在实际定义bean的所有别名中进行指定并不总是足够的。有时希望为在其他地方定义的bean引入别名。在大型系统中,配置被分割到每个子系统中,每个子系统都有自己的对象定义集。在基于XML的配置元数据中,您可以使用元素来实现这一点。
因此,如果您在bean配置中添加一个名称:
<bean id="customer" name="customer2" 
    class="jp.ne.goo.beans.Customer">
</bean>

或者为在其他地方定义的bean创建一个别名:
<alias name="customer" alias="customer2"/>

那么,“customer”和“customer2”将引用相同的bean实例。


1
谢谢Nathan,我不明白单例的用途是什么?单例的意思是整个应用程序只能创建一个实例(对象)。那么为什么Spring会为此创建两个对象呢?Spring内部使用了什么机制来实现这一点? - Raj
2
@Rajasekhar_b_1989:你的定义是错误的,我认为,请看我添加的Spring文档引用。 - Nathan Hughes

10

Spring的默认作用域是单例模式,它将为所有实例创建一个对象,除非您明确指定作用域为原型模式。你还没有发布Spring配置,请发布它,这将给出更好的想法。


1
在你的例子中,当你将值初始化为customer2时,它会改变customer对象的值。对于两个实例都是如此。你可以通过不向customer2初始化任何值来理解这一点。 - Anupama Rao
1
<bean id="customer" class="jp.ne.goo.beans.Customer"> <property name="custno" value="100"></property> <property name="custName" value="rajasekhar"> </property> </bean> <bean id="customer2" class="jp.ne.goo.beans.Customer"> </bean> - Anupama Rao
1
尝试运行上述代码并打印customer2的值,因为Spring单例为同一类创建一个对象,所以customer2也将具有customer的值。(通常情况下,当我们创建一个新实例并未初始化时,它将具有默认值,如null或int = 0) - Anupama Rao
1
@Anupama Rao 你自己试试怎么样?我相信这会很有启发性。 - Gab

4

在Spring中,“Singleton”指的是一个Spring容器中只有一个bean,而在Java中,“Singleton”指的是一个类加载器中只有一个对象。

因此,Spring中的“Singleton”与Java中的“Singleton”不同。不要混淆这两个概念。


3
您把两个不同的概念混淆了。
在Spring中,“singleton”这个词用于表示bean的范围,意思是该bean仅为整个应用程序创建一次。
“Singleton”的通常含义指的是GOF模式。它是一个面向对象的模式,保证类的实例只存在一个(至少在类加载器的范围内)。

1
谢谢Gab,根据GOF模式,单例意味着只应该创建一个实例,但在Spring中为什么会创建两个实例呢? - Raj
2
因为在Spring中,“单例模式”一词是应用于“Bean”,而不是类。这是两个不同的概念。 - Gab
1
谢谢提供信息Gab,最终类对象只加载到内存中而不是bean。在这种情况下,“class”和“bean”是什么意思? - Raj
1
一个Bean是一个类实例,其生命周期(创建、注入等)由容器管理。一个类与面向对象的概念相同... - Gab
“class object only loads into memory not bean”这个说法是不正确的。Bean是一个类实例,实例和类定义都会被加载到内存中,但是它们会被加载到不同的位置(分别是堆和“permgen”)。单词“bean”只是指出该实例由所提供的容器上下文进行管理的事实。 - Gab
谢谢@gab,这意味着对于每个bean的上述代码将创建两个对象。为什么Spring会创建两个对象,即使已经指定了作用域为Singleton?请尽量详细地解释一下如何为单例创建两个实例。 - Raj

2
您声明了两个相同类的bean,但这并不相同。
@Component("springTestClass")
public class SpringTestClass{
     private int randomNumber = 0;
     public SpringTestClass(){
       randomNumber = new Random().nextInt(2000);
     }

     public int getRandomNumber(){
       return this.randomNumber;
     }

}

如果在两个地方访问该bean,那么数字将是相同的。但你所做的是创建了两个独立的bean。

如果你想检查是否有效,请尝试:

public class Main{
   public static void main(String[] args){
     ApplicationContext ctx = ....;
     SpringTestClass testObject1 = (SpringTestClass)ctx.getBean("springTestClass");
     SpringTestClass testObject2 = (SpringTestClass)ctx.getBean("springTestClass");

    System.out.println(testObject1.getRandomNumber() == testObject2.getRandomNumber());
   }
}

如果是同一个实例,此代码应该返回true; 但在SpringTestClass中,您可以添加@Scope("prototype")注释。 输出将为false


2

Spring的Singleton Bean并不像Java Singleton那样工作。

如果我们编写:

ApplicationContext ctx = new ClassPathXmlApplicationContext("MyConfig.xml");
        Customer obj1= (Customer) ctx.getBean("customer");
        Customer obj2 = (Customer) ctx.getBean("customer2");
        System.out.println(obj1 == obj2);
        System.out.println(obj1+ "::" + obj2);

如果我们查看输出,它将返回2个不同的实例。根据Spring文档,Bean是单例的,只有一个共享实例将被管理,并且所有请求bean都具有与该bean定义匹配的ID或ID。这里有2个不同的ID可用。
Spring容器作为管理键值对,键为ID /名称,值为bean。最初的回答。

2
就像其他人提到的那样,根据您发布的代码应该创建两个bean。单例模式的定义如下(来自Spring文档:Singleton Scope)。

只有一个共享的单例bean实例被管理,并且所有对具有与该bean定义匹配的id或ids的bean的请求都会导致Spring容器返回该特定的bean实例。

为了更清楚地解释这一点,“共享实例”的含义在上面的段落之后进行了解释:

所有后续对该命名bean的请求和引用都将返回缓存的对象。

当创建一个单例bean时,只会实例化和缓存一个bean对象。这仅适用于bean本身,而不是bean可能是某个类的实例。例如,
<bean id="myBean" class="myPackage.myClass" />

<bean id="myOtherBean1" class="myPackage.myOtherClass1">
    <property name="beanReference1" ref="myBean" />
</bean>
<bean id="myOtherBean2" class="myPackage.myOtherClass2">
    <property name="beanReference2" ref="myBean" />
</bean>

在这个虚构的配置中,“myOtherBean1”和“myOtherBean2”引用了同一个“myBean” bean,因此是同一个“myPackage.myClass”的实例。如果你改变代码以添加第二个“myPackage.myClass” bean,它将与“myBean”不同。
要完全理解这一点,还需要参考Spring的另一个作用域:原型。根据Spring文档中的Prototype Scope
“非单例、原型作用域的bean部署会在每次请求该特定bean时创建一个新的bean实例。”
这意味着如果我们使用与上述相同的Spring XML,"myOtherBean1"和"myOtherBean2"将分别接收到自己独立的"myBean"副本,它仍然只是"myPackage.myClass"的一个实例。

0
以下示例展示了一个使用 @Bean 注解的方法被调用两次:
@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }

}

clientDao() 在 clientService1() 和 clientService2() 中各被调用了一次。由于该方法创建一个新的 ClientDaoImpl 实例并返回它,您通常期望有 2 个实例(每个服务一个)。这肯定会存在问题:在 Spring 中,默认情况下,实例化的 Bean 具有单例域。这就是魔法发生的地方:所有 @Configuration 类都在启动时以 CGLIB 进行子类化。在子类中,子方法首先检查容器中是否有任何缓存的(作用域) Bean,然后才调用父方法并创建一个新实例。请注意,从 Spring 3.2 开始,无需将 CGLIB 添加到类路径中,因为 CGLIB 类已重新打包,并直接包含在 spring-core JAR 中。

0

Spring的默认作用域是singleton。一旦bean被创建,同一个bean将在其整个生命周期中使用。


在回答问题之前,请先阅读已有的答案。如果这样做,我认为您会发现这并没有添加任何内容。答案应该提供其他答案所未涵盖的信息。 - Nathan Hughes

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