原型设计模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式之一。


原型模式

实际使用就像是Java的克隆clone,以及spring的@Scope("prototype")。

实际使用中,比如异步导出。逻辑都很相似:查询数据、导出数据。

然后可以写一个AsyncExportTask,类上面使用@Scope("prototype")注解。使用时通过ApplicationContext来获取不同的原型bean即可。然后使用异步线程执行即可。


最典型的就是 "原型作用域(Prototype Scope)" 的 Bean 管理。下面用通俗的话讲清楚它在 Spring 中的具体用法、场景和原理:

Spring 中默认的 Bean 是 "单例(Singleton)" 的 ------ 容器启动时创建一次,之后所有地方用的都是同一个对象。但有些场景下,我们需要 "每次获取都新建一个对象",而且这些对象的初始化逻辑相同(比如都需要加载相同的基础配置),这时候就可以用 原型作用域,本质就是 Spring 帮我们实现了 "克隆式创建"。

举个例子:

假设我们有一个 UserService,里面需要初始化一些基础配置(比如数据库连接信息、日志工具),但每次调用时都需要一个新的实例(比如避免多线程共享状态导致的问题)。

定义原型 Bean:用 @Scope("prototype") 标注,告诉 Spring:"这个 Bean 是原型的,每次获取都新创建一个"。

复制代码
@Service
@Scope("prototype") // 声明为原型作用域
public class UserService {
    private String dbUrl;
    private Logger logger;

    // 初始化方法:加载基础配置(只在创建时执行一次)
    @PostConstruct
    public void init() {
        this.dbUrl = "jdbc:mysql://localhost:3306/test"; // 基础配置
        this.logger = LoggerFactory.getLogger(UserService.class);
        System.out.println("初始化基础配置完成");
    }

    public void doSomething() {
        logger.info("执行操作,使用数据库:" + dbUrl);
    }
}

使用原型 Bean:每次从 Spring 容器中获取 UserService 时,都会得到一个 新的实例,但每个实例的 dbUrl、logger 等基础配置都是通过 init 方法初始化好的(相当于克隆了 "基础配置模板")。

复制代码
@Component
public class UserController {
    @Autowired
    private ApplicationContext context;

    public void test() {
        // 第一次获取:新建一个 UserService 实例,执行 init
        UserService service1 = context.getBean(UserService.class);
        // 第二次获取:再新建一个 UserService 实例,执行 init
        UserService service2 = context.getBean(UserService.class);

        // service1 和 service2 是不同的对象,但基础配置相同
        System.out.println(service1 == service2); // 输出:false
    }
}

二、为什么这是原型模式?

原型模式的核心是 "基于已有模板克隆新对象",而 Spring 原型作用域的逻辑完全符合:

"模板":就是 UserService 的类定义 + @PostConstruct 初始化的基础配置(相当于原型对象)。

"克隆":Spring 每次获取原型 Bean 时,都会通过反射创建一个新实例,并执行初始化方法(相当于 "克隆" 模板的基础配置),避免了手动重复写初始化代码。

"灵活微调":如果需要对新对象做个性化修改(比如给某个 UserService 实例改 dbUrl),直接修改新对象即可,不影响其他实例和 "模板"。

三、适合用原型作用域的场景(原型模式思想)

  • 多线程场景:避免状态共享如果 Bean 里有 "状态"(比如成员变量存储临时数据),单例模式下多线程并发访问可能导致数据混乱。用原型模式(原型作用域),每个线程拿到自己的实例,互不干扰。比如:OrderProcessor 处理订单时需要临时存储订单 ID,多线程同时处理时,每个线程用自己的 OrderProcessor 实例,避免订单 ID 串错。
  • 对象创建成本高:复用初始化逻辑如果 Bean 的初始化步骤复杂(比如加载配置文件、连接数据库、初始化缓存),每次手动 new 对象并重复初始化代码很麻烦。用原型作用域,Spring 会自动执行初始化逻辑,新对象直接复用这些基础配置。比如:ReportGenerator 需要加载报表模板、设置字体样式等初始化操作,原型模式让每次创建的实例都自带这些配置,不用重复写。
  • 临时对象:用完即弃有些对象只是临时用一次(比如处理单次请求的上下文对象 RequestContext),不需要单例复用,用原型模式创建更合适,避免单例占用内存。

四、Spring 原型模式的注意点

和单例 Bean 搭配时要小心如果单例 Bean 依赖了原型 Bean(比如单例 UserController 里注入原型 UserService),默认情况下,Spring 只会在单例初始化时获取一次原型 Bean,之后一直用同一个,不会自动刷新。

解决办法:每次需要时通过 ApplicationContext 手动获取原型 Bean,或者用 ObjectProvider 延迟获取。

原型 Bean 的生命周期自己管,单例 Bean 由 Spring 全程管理(从创建到销毁),但原型 Bean 一旦被创建并交给用户,Spring 就不再管了(不会自动调用 @PreDestroy 销毁方法),需要自己处理资源释放。

原型对比工厂

若新对象和某个已有对象高度相似,只是微调细节,且创建这个对象很麻烦 ------ 用原型模式(复制比新建省事);

若要创建的对象有多种类型,每种类型有固定的创建规则,但没有现成的 "模板对象"------ 用工厂模式(按规则造比维护多个原型省心)。

原型对比享元

原型侧重高效创建复杂对象,减少重复的初始化代码------多个相似复杂对象

享元侧重复用对象减少实例数量,降低内存占用------大量相似的简单对象

相关推荐
Coder_Boy_13 小时前
基于SpringAI的在线考试系统设计总案-知识点管理模块详细设计
android·java·javascript
Assby13 小时前
如何尽可能精确计算线程池执行 shutdown() 后的耗时?
java·后端
焰火199914 小时前
[Java]自定义重试工具类
java
SuperherRo15 小时前
JAVA攻防-Shiro专题&断点调试&有key利用链&URL&CC&CB&原生反序列化&加密逻辑
java·shiro·反序列化·有key·利用链·原生反序列化·加密逻辑
桦说编程15 小时前
简单方法实现子任务耗时统计
java·后端·监控
爱笑的眼睛1115 小时前
超越可视化:降维算法组件的深度解析与工程实践
java·人工智能·python·ai
盖世英雄酱5813615 小时前
物品超领取损失1万事故复盘(一)
java·后端
GISer_Jing15 小时前
AI Agent 目标设定与异常处理
人工智能·设计模式·aigc
CryptoRzz15 小时前
印度尼西亚(IDX)股票数据对接开发
java·后端·websocket·web3·区块链