何时使用 Spring 原型作用域?

Posted

技术标签:

【中文标题】何时使用 Spring 原型作用域?【英文标题】:When to use Spring prototype scope? 【发布时间】:2014-03-25 00:15:32 【问题描述】:

我想知道什么时候应该在 Spring 中准确使用 prototype 范围?我知道如果请求 bean,singleton 返回相同的对象实例。

那我们为什么要考虑prototype

通过示例进行解释将有助于理解对它的需求。

【问题讨论】:

【参考方案1】:

明确简单的定义:

原型范围 = 每次注入/查找时都会创建一个新对象。每次都会使用新的SomeBean()

单例范围 = 每次注入/查找时都返回相同的对象。在这里它会实例化一个SomeBean 的实例,然后每次都返回它。

原型 bean 在使用时创建。因此,当您希望拥有有状态的 bean 时,有时强烈需要拥有原型范围,或者当您不想在 bean 中缓存任何值时。原型 bean 可以与一个会话或某个调用相关联。

例子:

数据访问对象 (DAO) 通常不配置为原型,因为典型的 DAO 不保持任何会话状态;对这位作者来说,重用单例图的核心更容易。

【讨论】:

那么应该创建哪种类型的bean作为原型? 只有那些你想每次都有新鲜实例的人。老实说,我不会在 99% 的情况下使用原型。 一个好的做法是通过构造函数传递依赖关系。因此,您永远不应该使用作用域原型。相反,您应该使用新的或单例工厂。 这个答案如何获得支持?它根本没有指定何时使用原型范围。它只是通过说明为什么 DAO 通常不配置为根本无法回答问题的原型来转移问题。 “Example”后面的部分是从 Spring 文档的 Section 3.5.2 The prototype scope 复制和粘贴的,没有署名。它还省略了实际上是示例的图像。【参考方案2】:

有一些有趣的用例,通过使用范围原型,您将构建更好、更可靠的应用程序设计/架构,例如实时系统。

假设您必须建立一个实时车辆跟踪系统,您将有 2.000.000 辆汽车每 5 秒共享信息, 在服务器端,您将使用两组或更多组不同的配置,一组用于汽车,另一组用于卡车。

基于这个简单的示例,如果您将应用程序设计为通过原型模式在内存中使用不同的配置组,您将获得更好的性能。

因此,在这种情况下,例如,每当服务器收到来自卡车的新消息时,服务器都会从 VehicleGrupConfiguration 实例的哈希映射中获取内存中的配置实例,然后应用此消息必须具有的配置行为,例如:超时、重试...等。

我想强调一下,有很多方法可以实现这种情况,但是这个例子表明原型模式在性能和设计模式方面非常强大。

【讨论】:

为了提高实时系统的性能,代码的设计应尽量减少事务期间对象实例化的频率。每次从 Spring 框架请求原型时,都会对原型进行实例化。原型的这种行为会降低实时性能。评论员是正确的,为了提高性能,应该从内存中访问配置数据,但要小心使用原型来实现该设计要求。相反,请考虑定义一组相同类型的单例,每个单例在启动时创建并具有不同的状态。 嗨@MarkNorman你能不能分享一个例子我怎么能想出这个解决方案“相反,考虑定义一组相同类型的单例,每个在启动时创建具有不同的状态”听起来很您的想法很有趣,非常感谢您分享您的反馈。 您将在此处找到示例:baeldung.com/spring-data-jpa-multiple-databases。请注意,有两个LocalContainerEntityManagerFactoryBean 类型的配置bean,一个名为userEntityManager,另一个名为productEntityManager【参考方案3】:

如文档所述,创建具有原型范围的 Bean Foo 与调用相同:

Foo foo = new Foo(dependency1, dependency2, ...);
foo.initialize(dependency7, dependency8...);

使用原型作用域 bean 而不是 new 的唯一充分理由是,用于创建和初始化实例的依赖项应保留在需要新实例的代码之外。

举个例子:

// need to explicitly mention dependencies here
public void createdWithNew(Dependency dependency1, Dependency dependency2) 
  Foo foo = new Foo(dependency1, dependency2, ...);
  foo.doSomething();


// Dependencies managed in class Foo by Spring
public void createdWithSpring(Foo foo) 
  foo.doSomething();


例如,如果您想编写类似于 EJB2 Java Entity bean 的持久性代码,例如

Person p = ...
p.setName("John Doe");
p.save(); // write to DB

而不是使用JPA方式

Person p = new Person();
p.setName("John Doe");
personService.save(p); // write to DB

在实体 bean 代码风格中,person 实例需要知道它应该如何被持久化,因此它需要被注入写一个人的代码不应该知道的持久化细节。

另一个例子: 如果您想在应用程序的许多地方使用非线程安全的 SimpleDateFormat Java 类,并使用配置文件中的格式模式(可能根据其他条件使用不同的格式)。与其在所有这些地方创建一个新的格式实例,还从文件(或弹簧属性)加载格式字符串,您可以使用原型范围每次都获取一个新的实例,设置通用格式的详细信息在一个地点。

【讨论】:

嗨@tkruse 我想明白你在写“当用于创建和初始化实例的依赖项应该保留在需要新实例的代码之外时”是什么意思。我有一个类似的例子,我使用一个新的而不是一个bean,因为每次调用它时我都需要一个新的类实例,并且它必须为每个会话保持状态。我的用例与***.com/questions/69026436/… 非常相似,我不确定原型的使用。 我试着举更多例子,有帮助吗?

以上是关于何时使用 Spring 原型作用域?的主要内容,如果未能解决你的问题,请参考以下文章

spring bean的作用域之间有啥区别

作用域链闭包和原型链

spring:使用会话和请求作用域

Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.5.3 依赖于原型Bean的单例Bean

spring的bean作用域有几种,开发中需要注意啥?

Spring Bean作用域