为什么不推荐你在使用 Spring 时主动从容器中获取 Bean?

B站影视 内地电影 2025-10-28 23:38 1

摘要:// ❌ 错误示例:聚合根中直接获取领域服务public class Order { public void cancel { // 试图获取领域服务发送通知 NotificationService service = ApplicationContext.g

下面是一段使用 DDD 时常见的错误代码示例,你甚至可以在很多 DDD 的分享文章中看到类似用法:

// ❌ 错误示例:聚合根中直接获取领域服务public class Order { public void cancel { // 试图获取领域服务发送通知 NotificationService service = ApplicationContext.getBean(NotificationService.class); service.sendCancelNotification(this.id); }}

虽然DDD 不是本文关注的对象,但是这段代码其实本身是违反 DDD 设计原则的:

1.聚合根(领域对象)变成了“主动调用者”,违反了“聚合根不应依赖外部服务”的原则。

2.领域对象不应知道 Spring 容器的存在。

关于这段代码你可能会遇到一个极为常见的问题:在启动或停服的时候出现service 对象获取不到的异常。其原因也比较好理解:因为 Spring 无法感知到 order 对象对 service 对象的依赖可能导致 运行至第5行时,service 对象已被销毁。

不推荐主动从 Spring 容器中获取 Bean( ApplicationContext.getBean),应该是一个被广泛强调的规范。本文从将从多个维度 解释为什么不推荐主动从 Spring 容器中获取 Bean。

Spring 的核心思想是 控制反转:将对象的创建、依赖关系的管理、生命周期控制等“控制权”交给容器,而不是由程序员在代码中显式控制。主动调用 getBean 相当于 把控制权又拿回自己手里,破坏了 IoC 的初衷。Spring 通过 DI 实现松耦合:组件之间通过接口或抽象类协作,具体实现由容器注入。使用 getBean 是 “拉取式”依赖获取(Pull),而 DI 是 “推送式”依赖注入(Push)。“拉取”模式让组件知道容器的存在,增加了对框架的依赖。如果代码中直接调用 ApplicationContext.getBean,那么测试该方法时必须提供一个有效的 ApplicationContext。这通常意味着必须启动整个 Spring 上下文(集成测试),而不是轻量级的单元测试。单元测试的目标是 隔离被测代码,而 getBean 引入了外部依赖(容器),破坏了隔离性。使用 DI 时,可以轻松用 Mockito 等工具 Mock 依赖。而 getBean 返回的是容器中真实的 Bean,难以替换为测试替身(Test Double)。// 难以测试public void process { UserService user = context.getBean(UserService.class); user.save(...);}// 易于测试public class MyService { private final UserService userService; public MyService(UserService userService) { this.userService = userService; } public void process { userService.save(...); } // 可直接传入 Mock}

例如:一个类看起来没有依赖,但运行时却通过 getBean 调用了数据库服务,这会让维护者困惑。

如果错误地在多线程环境中缓存 ApplicationContext 并并发调用 getBean,而 Bean 本身不是线程安全的(如 prototype 或有状态 Bean),可能引发并发问题。而 DI 注入的单例 Bean 通常设计为无状态,更安全。场景说明推荐做法运行时动态选择实现类如根据用户类型选择不同策略使用策略模式 + 工厂类,工厂类通过 DI 获取 ApplicationContext,业务代码不直接调用遗留系统或第三方回调如 Servlet Filter、JDBC 回调等无法注入的上下文通过 ApplicationContextAware 获取上下文,但仅限于基础设施层工具类需要访问 Spring Bean如静态工具方法需调用 Service尽量避免;若必须,可持有静态 ApplicationContext 引用(但需注意初始化顺序和内存泄漏)

✅ 最佳实践:将 getBean 封装在专用工厂或服务中,业务代码永远不直接接触 ApplicationContext。

八、针对开头的错误示例的正确建议通在ApplicationService中添加Service依赖注入,替代在聚合根中ApplicationContext.getBean获取 bean。

这样的逻辑本身也遵守 DDD 原则:

领域对象(如聚合根)不应直接调用服务。

如果需要调用领域服务,应在 应用层 完成协调。

// 应用服务(Application Service)@Service@Transactionalpublic class OrderApplicationService { private final OrderRepository orderRepository; private final NotificationService notificationService; // 通过 DI 注入 public void cancelOrder(String orderId) { Order order = orderRepository.findById(orderId); order.validateCanCancel; // 领域逻辑在聚合根内 orderRepository.save(order); notificationService.sendCancelNotification(orderId); // 应用层调用 }}维度问题设计原则违背 IoC/DI,破坏控制反转可测试性难以单元测试,Mock 困难耦合度业务代码强依赖 Spring 容器架构清晰度隐藏依赖,破坏分层生命周期可能获取未初始化或错误作用域的 Bean可维护性代码难以理解和重构可移植性无法脱离 Spring 环境运行

来源:ZenN

相关推荐