
本文探讨 spring 应用中何时应使用容器管理的单例 bean,何时应手动创建有状态的短生命周期实例,并分析其面向对象设计合理性、作用域选择策略及实际性能影响。
本文探讨 spring 应用中何时应使用容器管理的单例 bean,何时应手动创建有状态的短生命周期实例,并分析其面向对象设计合理性、作用域选择策略及实际性能影响。
在 Spring 典型 Web 应用中,Service、Repository、Controller 等组件普遍以单例(Singleton)作用域由 IoC 容器统一管理并自动注入(@Autowired)。这类 Bean 通常无状态(stateless),仅封装可复用的业务逻辑或数据访问能力——例如 UserService.findUserById(Long id) 不依赖自身字段,而是通过组合 UserRepository 完成操作。这种设计并非“违背面向对象”,而是对关注点的合理分离:Spring 负责对象生命周期与协作关系(即“谁来协调”),而开发者仍可自由创建承载状态与行为的领域对象(即“谁来承载”)。
✅ 正确的设计模式是混合使用:
- 容器管理 Bean(推荐单例):用于无状态的基础设施类(如 JdbcTemplate、RestTemplate)、服务编排层(OrderService)、数据访问层(ProductRepository)。它们天然适合共享,且由 Spring 管理事务、AOP、配置注入等横切关注点。
-
手动创建实例(推荐原型/临时对象):用于有状态的、请求/业务上下文相关的对象,例如:
// 每次下单创建独立的订单计算上下文,持有价格、折扣、运费等瞬时状态 OrderCalculationContext context = new OrderCalculationContext( cart.getItems(), user.getPreferredCurrency(), getCurrentPromotion() ); BigDecimal total = context.calculateTotal(); // 行为封装在实例中此类对象不应交给 Spring 管理(避免状态污染),而应在需要时按需构造,用完即弃。
⚠️ 关于作用域选择:
单例(@Scope("singleton"),默认):适用于无状态、线程安全的组件。绝大多数 Spring Bean 应为此作用域。
-
原型(@Scope("prototype")):每次 getBean() 都返回新实例。仅当必须通过 Spring 容器注入依赖(如 @Autowired DAO)且需隔离状态时才考虑,例如:
@Component @Scope("prototype") public class ReportGenerator { @Autowired private DataSource dataSource; // 依赖注入仍生效 private String reportId; // 实例级状态 public void init(String id) { this.reportId = id; } public byte[] generate() { /* 使用 dataSource + reportId */ } }⚠️ 注意:@Scope("prototype") 的 Bean 不会被 Spring 自动管理销毁,也不支持 @PostConstruct/@PreDestroy(除非显式调用 ConfigurableBeanFactory.destroyScopedBean())。因此,若无强依赖注入需求,优先选择 new 创建。
⚡ 性能考量:
- 单例 Bean:零创建开销,内存占用恒定,性能最优。
- 频繁 new 短生命周期对象(如每个 HTTP 请求创建数个 DTO、Builder、Context):现代 JVM(尤其是 G1/ZGC)垃圾回收效率极高,只要对象不逃逸到老年代且生命周期短(Eden 区内即可回收),性能影响微乎其微。实测表明,在 QPS 1000+ 场景下,每请求创建 5–10 个轻量对象几乎无可观测延迟。
- 滥用 @Scope("prototype"):因 Spring 需执行完整 Bean 生命周期(解析依赖、应用后置处理器、代理等),其创建成本显著高于 new,且可能引发意外的内存泄漏(如未及时释放引用)。除非必须依赖注入,否则避免使用原型作用域。
? 总结建议:
- 默认用单例 Bean —— 管理无状态协作组件;
- 有状态领域对象用 new —— 明确生命周期,避免容器干扰;
- 慎用 @Scope("prototype") —— 仅当需 Spring 注入 且 隔离状态时选用;
- 性能无需过度担忧 —— JVM 对短生命周期对象优化成熟,优先保证设计清晰性与可维护性。
良好的 Spring 架构,不是“全盘托管”,而是让容器做它最擅长的事(解耦、复用、横切),把状态和行为交还给纯粹的 Java 对象——这才是面向对象与框架能力的协同之道。


















