You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

依赖注入 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,可轻松处理多数据源场景:

  1. 定义限定符区分不同数据源:
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, PARAMETER, METHOD})
public @interface PrimaryDB {}

@Qualifier
@Retention(RUNTIME)
@Target({FIELD, PARAMETER, METHOD})
public @interface SecondaryDB {}
  1. 在仓库实现类上标记对应限定符:
@ApplicationScoped
@PrimaryDB
public class PrimaryUserRepository implements UserRepository { ... }

@ApplicationScoped
@SecondaryDB
public class SecondaryUserRepository implements UserRepository { ... }
  1. 在工厂/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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 18:05:18