EJB 的 CDI 生产者

Posted

技术标签:

【中文标题】EJB 的 CDI 生产者【英文标题】:CDI producer for EJB 【发布时间】:2016-05-30 17:02:44 【问题描述】:

我正在尝试使用 POJO 作为 CDI 生产者来注入正确的 EJB,但我得到 org.jboss.weld.exceptions.UnsatisfiedResolutionException: WELD-001308

这是我的制作人 POJO

public class STGatewayUtilProducer 

    @Produces
    @Chosen
    public ISTGatewayUtil getISTGatewayUtil(Instance<STGatewayWSUtil> ws, Instance<STGatewayMQTTUtil> mqtt, ConfigurationManager cm) 
        switch(cm.getGatewayProtocol()) 
            case ConfigurationManager.GATEWAY_PROTOCOL_TYPE_MQTT:
                return mqtt.get();
            default:
                return ws.get();
        
    


这是限定符定义:

@Qualifier
@Target( TYPE, METHOD, PARAMETER, FIELD )
@Retention(RUNTIME)
@Documented
public @interface Chosen 

这些是 EJB 声明:

@Stateless
public class STGatewayMQTTUtil implements Serializable, ISTGatewayUtil 
    ...


@Stateless
public class STGatewayWSUtil implements Serializable, ISTGatewayUtil 
    ...

最后,这是我注入 EJB 的方式:

@Inject
@Chosen
private Instance<ISTGatewayUtil> gtwUtil;

我在使用 JBoss AS 7 和 WildFly 10 时都遇到了问题。

编辑

我找到了问题的核心!我声明了一个通用的 abstract 父类,它实现了 ejb 接口并让我的会话 bean 扩展它:使用这种结构无法解析 bean。

相反,如果我在会话 bean 上移动 implements 子句,问题就会消失:有人可以解释一下我的类层次结构有什么问题吗?

【问题讨论】:

@ProducesInstance 不能很好地协同工作,请使用其中之一。为什么你需要Instance&lt;ISTGatewayUtil&gt; gtwUtil 而不是ISTGatewayUtil gtwUtil @Geinmachi 因为我希望它被延迟加载:这不是正确的做法吗? 【参考方案1】:

引用specification

3.2.2。会话 bean 的 bean 类型

会话 bean 的不受限制的 bean 类型集包含所有 bean 的本地接口及其超接口。如果会话 bean 有一个无接口视图,无限制的 bean 类型集 包含 bean 类和所有超类。此外, java.lang.Object 是每个会话 bean 的 bean 类型。

远程接口不包含在 bean 类型集中。

因此,由于您的两个会话 bean 都有本地接口,因此它们的 bean 类型集中没有它们的类。因此,您无法通过他们的类解决它们是正常的。

您需要在会话 bean 定义中添加额外的信息,以便能够区分它们或使用 @LocalBean 注释将它们声明为无接口视图 EJB。 这是您的代码的新版本,将您的 EJB 声明为 NIV

@Stateless
@LocalBean 
public class STGatewayMQTTUtil implements Serializable, ISTGatewayUtil 
    ...


@Stateless
@LocalBean
public class STGatewayWSUtil implements Serializable, ISTGatewayUtil 
    ...

您的生产者不需要注入 2 Instances&lt;T&gt;。您可以注入两个 bean 并返回选择的一个。

public class STGatewayUtilProducer 

    @Produces
    @Chosen
    public ISTGatewayUtil getISTGatewayUtil(STGatewayWSUtil ws, STGatewayMQTTUtil mqtt, ConfigurationManager cm) 
        switch(cm.getGatewayProtocol()) 
            case ConfigurationManager.GATEWAY_PROTOCOL_TYPE_MQTT:
                return mqtt;
            default:
                return ws;
        
    


或像这样使用Instance&lt;T&gt;

public class STGatewayUtilProducer 

    @Produces
    @Chosen
    public ISTGatewayUtil getISTGatewayUtil(Instance<ISTGatewayUtil> gw, ConfigurationManager cm) 
        switch(cm.getGatewayProtocol()) 
            case ConfigurationManager.GATEWAY_PROTOCOL_TYPE_MQTT:
                return gw.select(STGatewayMQTTUtil.class).get();
            default:
                return gw.select(STGatewayWSUtil.class).get();
        
    


使用 bean 实例生成新 bean 时要小心,它应该具有 @Dependent 范围(以避免在生成的 bean 上叠加 2 个生命周期)或注入 @New 关键字。在这里,您的会话 bean 位于 @Dependent 范围内,因为它们没有指定任何范围。

【讨论】:

非常感谢安托万!我知道我必须阅读更多关于 EJB 视图的信息,因为它对我来说不是很清楚......我尝试使用本地接口 EJB 而不是 NIV 从两个类中删除 @LocalBean 并将 @Local 放在接口 @987654333 上@ 但它抛出了同样的异常:你能解释一下为什么吗?另外,我不明白您关于叠加 2 个生命周期的最后警告):您能否进一步解释一下或链接我其他阅读内容? 如果您从会话 bean 中删除 @LocalBean,您将返回到之前的用例。将@Local 添加到接口只会使其显式。我不是 EJB 专家(仅 CDI 规范负责人),您可以查看此博客文章以获取有关 EJB 视图的更多信息:piotrnowicki.com/2013/03/… 如果你在@RequestScoped 中声明一个CDI bean,它的一个给定实例将只存在于http 请求的时间。这个“生死”(生命周期)是由 CDI 容器自动管理的。如果您在 @ApplicationScoped bean 的生产者中注入 @RequestScoped bean(通过示例中的参数),并且您使用该 bean 的全部或部分来创建您的 @ApplicationScoped bean,您将获得奇怪的结果,因为两个bean都在不同的范围内。 @New 是为此而引入的,但自 CDI 1.1 起,它已被弃用,我们更喜欢将 @Dependent bean 用于这种用例 我理解了混合生命周期的问题。相反,我还不清楚我应该如何将 CDI 生产者与仅公开本地接口的会话 bean 一起使用。或者生产者只能用于声明(也)@LocalBean? 不是生产者的问题,而是bean解析的问题。当 EJB 与 CDI 一起使用时,除非它是无接口视图 (NIV) EJB(使用 @LocalBean 定义),否则在解析注入点时不会考虑其类。在这里,您的 2 个 EJB 不是(NIV)EJB 并且实现了相同的接口ISTGatewayUtil,因此没有任何方法可以区分它们。可以通过在 EJB 上引入 2 个接口或使用 2 个不同的限定符来区分。【参考方案2】:

您的案例场景非常适用于 CDI 限定符,如果您需要事务管理,您仍然可以维护 ejb 会话 bean(如果您不需要任何事务逻辑,那么我会首先取消 ejb )。

也就是说,我会这样设计你的场景:

@Qualifier
@Retention(RUNTIME)
@Target(FIELD, METHOD, PARAMETER, TYPE)
public @interface ISTGateway 

   ISTGatewayType value()

   enum ISTGatewayType 
       MQT,
       WS
   

用法如下所示:(注意 ejb 已用 @Dependent 注释以使 CDI 容器自动检测它们)

@Stateless
@Dependent 
@ISTGateway(MQT)
public class STGatewayMQTTUtil implements Serializable, ISTGatewayUtil 
    ...


@Stateless
@Dependent
@ISTGateway(WS)
public class STGatewayWSUtil implements Serializable, ISTGatewayUtil 
    ...

你的生产者应该是这样的:(这里的生产者的好处是你永远不需要更新它,如果你添加了一个新的 ISTGatewayUtil)

@ApplicationScoped
public class STGatewayUtilProducer 
    @Any
    @Inject
    private Instance<ISTGatewayUtil> istGatewayUtils;

    @Inject
    private ConfigurationManager configurationManager;

    @Chosen
    @Produces
    public ISTGatewayUtil getISTGatewayUtil() 
        final ISTGateway istGateway = new ISTGatewayImpl(cm.getGatewayProtocol());
        return istGatewayUtils.select(istGateway).get();
    

    private static final class ISTGatewayImpl extends AnnotationLiteral<ISTGateway> implements ISTGateway 

       private final ISTGatewayType istGatewayType;

       private ISTGatewayImpl( final ISTGatewayType istGatewayType) 
          this.istGatewayType = istGatewayType;
       

       public ISTGatewayType value() 
           return istGatewayType;
       
      

【讨论】:

以上是关于EJB 的 CDI 生产者的主要内容,如果未能解决你的问题,请参考以下文章

启用 CDI 注入到由生产者方法创建的 bean

在 JEE 中使用生产者和具有 CDI 的多态性添加有状态 bean

cdi bean 中的资源注入

CDI 和 EJB 如何比较?相互作用?

JSF、CDI 和 EJB 容器:应该使用它们的哪种组合?

哪个是 CDI @Produces 注释的 Spring 等效项?