在 EJB 调用中返回 CDI bean 引用

Returning CDI bean reference in EJB call

Java EE 中是否允许以下​​内容?

我有一个 @Singleton 会话 bean,用作注册表并自动发现在整个应用程序中使用的某些策略,如下所示:

public interface Strategy {
    Class<?> supportedType();
}

@Singleton
public class StrategyRegistry {

    @Inject
    private Instance<Strategy> strategies;

    private Map<Class<?>, Strategy> registry = new HashMap<>();

    @PostConstruct
    public void startup() {
        Iterator<Strategy> it = strategies;
        while (it.hasNext()) {
            Strategy s = it.next();
            registry.put(s.supportedType(), s);
        }
    }

    // return provided strategy to caller
    public Strategy strategyFor(Class<?> clz) {
        return registry.get(clz);
    }
}

现在我的问题是是否允许将 @Injected CDI bean 共享给其他客户端(通过 #strategyFor(...)),或者这是否会导致意外行为或副作用?

这些策略是无状态和线程安全的,因此通常应该可以同时使用它们。

请注意,上面 CDI bean 的所有者是 @Singleton 并且 CDI bean 当前是 @Dependent,因此如果我正确阅读规范,它们应该在整个生命周期内可用应用程序。

最初我们使用 EJB 无状态 bean,strategyFor(...) 仅将代理返回给实际的 bean,我认为这相当安全。这在具有最新 WELD 版本的 Wildfly 8 上不再可能,因此我们切换到 CDI bean。然而,对于 CDI bean,返回的是实际的 bean,而不是代理。

如果不允许这样做,以下是安全的替代方案吗?

@Inject private BeanManager beanManager;

public Strategy strategyFor(Class<?> clz) {
    Strategy s = registry.get(clz);
    if (s == null) return null;
    return beanManager.getReference(s.getClass());
}

见下文

@Qualifier
@Retention(RUNTIME)
@Target({FIELD,METHOD,TYPE,PARAMETER})
public interface StrategyContext {

    //Nonbinding and default so that you can have one producer method for all strategies
    @NonBinding
    Class<?> supportedType() default Object.class;
}

@Singleton
public class StrategyRegistry {

    private Map<Class<?>, Strategy> registry = new HashMap<>();

    @Inject
    void startup(@Any final Instance<Strategy> strategies) {
        for(Strategy strategy:strategies)
            registry.put(Strategy.supportedType(), Strategy);
        }
    }

    @Produces
    @StrategyContext
    @ApplicationScoped
    public Strategy strategyFor(final InjectionPoint ip) {
       final StrategyContext sc = ip.getAnnotated().getAnnotation(StrategyContext.class);
       final Class<?> supportedType = sc.supportedType();
       return registry.get(supportedType);
    }
}

然后随处使用。

@Stateless
public class MyService {

   @Inject
   @StrategyContext(supportedType=MySupportedType.class)
   private Strategy strategy;

}

关于生产者和注入点:

NOTE If at runtime, the strategy may not exist and hence the method may return null, then annotate the producer with @Dependent, not @ApplicationScope and hence at the injection point, don't inject the raw strategy, but:

  @Inject
    @StrategyContext(supportedtype=MySupportedType.class)
    private Instance<Strategy> strategy

    ...
    if(!strategy.isUnsatisfied()) { strategy.get().doSomething();}

我不明白为什么不这样做,因为 @Depend(默认范围)是一个伪范围,因此 bean 管理器不再对该 bean 感兴趣。它从那里开始的生命周期完全取决于外部 bean 或其他直接引用它的 类 - 它是一个普通的旧 POJO