原型引用是否拷贝取决于业务语义而非技术能力:共享资源(如datasource)不应拷贝,可变业务对象(如address)需深拷贝,不可变对象(如string)可安全浅拷贝,克隆目的决定策略——隔离用深拷贝,复用则保留引用。

原型引用是否应该被拷贝,取决于业务语义和对象的可变性,不是技术上“能不能”,而是逻辑上“该不该”。
看字段是否参与对象状态表达
如果某个引用字段代表的是原型对象的“身份标识”或“共享上下文”,比如数据库连接池、配置管理器、日志器等全局资源,那它就不该被拷贝——克隆体应复用同一实例,避免资源浪费或状态不一致。
- 典型例子:Spring 中
@Scope("prototype")的 Bean,其内部注入的@Singleton依赖(如DataSource)不会被复制,只保留引用 - 反例:若一个
Person对象持有一个可变的Address,而克隆后要独立修改地址(如生成不同收货地址),那这个引用就必须深拷贝
看引用对象本身是否可变
不可变对象(如 String、LocalDateTime、自定义 final 类)即使被浅拷贝,也不会引发副作用,因为无法修改其内部状态。这种引用可以安全共享,无需拷贝。
-
String name = "张三"浅拷贝没问题;但Address address = new Address("北京")就需谨慎——除非你确认克隆体绝不会调用address.setCity() - JDK 14+ 的
Record类天然适合浅拷贝,因其字段默认不可变
看克隆用途是“隔离”还是“复用”
原型模式本质是为高效创建新实例,但目的不同,策略就不同:
- 用于临时计算、导出、预览等场景 → 需要完全隔离 → 倾向深拷贝
- 用于请求上下文、线程局部配置、模板填充等场景 → 共享基础配置 + 覆盖局部属性 → 可采用“半深拷贝”:基础引用保留,业务数据深拷贝
- Spring 的
PrototypeBean 默认就是这种混合策略:自身状态独立,注入的单例依赖仍复用
别让 clone() 做它不该做的事
Java 的 Object.clone() 天然只支持浅拷贝,强行在 clone() 方法里递归深拷贝所有引用,会导致耦合高、维护难、易出错(比如循环引用、非 Serializable 字段)。
- 更清晰的做法:把“哪些引用要复制”显式写在构造函数或 builder 中,例如
new Person(original, true /* copy address */) - 或用现代工具如 MapStruct,在 DTO 映射时精确控制每个字段的拷贝行为
- 序列化实现深拷贝虽简单,但性能差、要求严格(必须
Serializable)、不支持 transient 字段 —— 仅适合低频、简单结构











