原型设计模式

原型模式(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 销毁方法),需要自己处理资源释放。

原型对比工厂

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

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

原型对比享元

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

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

相关推荐
笃行客从不躺平2 小时前
SQL 注入复习
java·数据库·sql
小毅&Nora2 小时前
【后端】使用 Easy Rules 构建灵活的业务规则引擎 — Spring Boot 集成实践
java·spring boot·后端
多喝开水少熬夜2 小时前
算法-哈希表和相关练习-java
java·算法·散列表
guangzan3 小时前
常用设计模式:工厂方法模式
设计模式
shayudiandian3 小时前
【Java】面向对象编程
java
asom223 小时前
互联网大厂Java求职面试实战:Spring Boot到Kubernetes的技术问答
java·spring boot·kubernetes·oauth2·电商·microservices·面试技巧
I_Jln3 小时前
CountDownLatch:让多线程同步如此简单
java
虎子_layor3 小时前
轻量级哈希扰动工具:Hashids,快速上手
java·spring
逻极3 小时前
VS Code之Java 开发完全指南:从环境搭建到实战优化
java·开发语言