依赖注入 vs 非托管类:设计模式场景下的依赖处理难题
装饰器/命令/策略模式下的CDI依赖注入解决方案
CDI.current()的合规性问题
CDI.current()本质是Service Locator模式的实现,虽能临时解决依赖获取问题,但属于不推荐的反模式变体:
- 隐藏真实依赖:其他开发者无法直接从类定义看出所需服务,可读性与维护性下降
- 耦合CDI容器:单元测试需模拟整个CDI上下文,测试成本陡增
- 易引发作用域错误:比如在非请求上下文获取请求作用域实例,导致运行时异常
除非是遗留代码这类完全无法通过常规注入解决的边缘场景,否则别用这种方式。
替代方案:让依赖注入适配模式结构
装饰器、命令、策略这类模式虽要求构造函数实例化,但可通过以下方式让CDI依赖注入无缝兼容:
1. 用CDI管理的工厂类创建实例
创建由CDI托管的工厂类,注入所有所需依赖,再在工厂方法中将依赖传递给模式类的构造函数。既保持模式类的构造初始化要求,又避免直接耦合容器:
@ApplicationScoped public class OrderCommandFactory { @Inject private OrderRepository orderRepo; @Inject private InventoryService inventoryService; public CreateOrderCommand createCreateOrderCommand(OrderRequest request) { return new CreateOrderCommand(request, orderRepo, inventoryService); } public CancelOrderCommand createCancelOrderCommand(String orderId) { return new CancelOrderCommand(orderId, orderRepo, inventoryService); } }
使用时直接注入工厂类,调用对应创建方法即可,完全符合CDI规范。
2. 利用CDI Producer扩展动态创建实例
如果需要根据动态条件创建不同策略/命令实例(比如策略模式根据用户类型选择策略),可通过CDI的Producer机制封装实例创建逻辑,同时注入依赖:
@ApplicationScoped public class UserStrategyProducer { @Inject private UserRepository userRepo; @Inject private AdminAuditService auditService; @Produces @RequestScoped public UserStrategy getStrategy(@Inject @StrategyType String userType) { return switch(userType) { case "ADMIN" -> new AdminStrategy(userRepo, auditService); case "REGULAR" -> new RegularUserStrategy(userRepo); default -> throw new IllegalArgumentException("Unknown user type: " + userType); }; } }
其中@StrategyType是自定义限定符,用于传递动态参数。需要使用策略的地方直接注入UserStrategy,CDI会自动通过Producer创建带依赖的实例。
3. 装饰器模式用CDI原生支持
CDI本身内置装饰器模式支持,无需手动实例化装饰器类。用@Decorator标记装饰器,@Delegate注入被装饰的目标实例,容器会自动处理装饰链:
@Decorator @Priority(100) // 控制装饰器执行顺序 public class LoggingOrderServiceDecorator implements OrderService { @Inject @Delegate private OrderService delegate; // 被装饰的原始服务 @Inject private Logger logger; @Override public void createOrder(Order order) { logger.info("Starting order creation: " + order.getId()); delegate.createOrder(order); logger.info("Order " + order.getId() + " created successfully"); } }
这种方式完全遵循CDI规范,无需手动管理装饰器实例,还能通过@Priority控制多个装饰器的执行顺序。
多数据源/仓库的处理
结合自定义限定符和工厂/Producer,可轻松处理多数据源场景:
- 定义限定符区分不同数据源:
@Qualifier @Retention(RUNTIME) @Target({FIELD, PARAMETER, METHOD}) public @interface PrimaryDB {} @Qualifier @Retention(RUNTIME) @Target({FIELD, PARAMETER, METHOD}) public @interface SecondaryDB {}
- 在仓库实现类上标记对应限定符:
@ApplicationScoped @PrimaryDB public class PrimaryUserRepository implements UserRepository { ... } @ApplicationScoped @SecondaryDB public class SecondaryUserRepository implements UserRepository { ... }
- 在工厂/Producer中注入不同仓库,根据业务逻辑传递给模式类:
@ApplicationScoped public class DataSyncStrategyFactory { @Inject @PrimaryDB private UserRepository primaryRepo; @Inject @SecondaryDB private UserRepository secondaryRepo; public DataSyncStrategy createSyncStrategy() { return new TwoWaySyncStrategy(primaryRepo, secondaryRepo); } }
总结
- 尽量避免
CDI.current(),它带来的维护与测试成本远大于临时便利 - 优先使用工厂类、CDI Producer或原生模式支持(如装饰器),既符合设计模式要求,又保持代码的松耦合与可测试性
- 多数据源通过自定义限定符+工厂/Producer的组合处理,清晰且符合CDI规范
内容的提问来源于stack exchange,提问作者Simon
相关产品推荐
相关产品推荐

