将EJB(JNDI)和CDI集成的最佳方法

3

目前我们有一个部署架构,一堆数据导向的服务通过RMI暴露给业务服务。这两种类型(数据导向的服务和业务服务)都是无状态会话bean。每个数据服务接口包(包含远程接口)还有一个定位器,用于执行JNDI查找。我们这样做是为了可以从业务服务逻辑的任何地方调用数据导向的服务。

以下是定位器的示例:

public final class OMRLocator {

    private static final Logger LOG = Logger.getLogger( OMRLocator.class );

    private static final String ORG_WILDFLY_INITIAL_CTX_FACTORY = "org.wildfly.naming.client.WildFlyInitialContextFactory";

    private OMRLocator() {
    }

    @Produces
    public static OrganisationsAndMandatesRegister locate() {
        try {
            Properties ctxProp = new Properties();
            ctxProp.put( Context.INITIAL_CONTEXT_FACTORY, ORG_WILDFLY_INITIAL_CTX_FACTORY );
            InitialContext ctx = new InitialContext( ctxProp );
            return (OrganisationsAndMandatesRegister) ctx.lookup( OrganisationsAndMandatesConstants.REMOTE_NAME );
        }
        catch ( NamingException ex ) {
            LOG.log( Level.WARN, "Cannot reach: " + OrganisationsAndMandatesConstants.REMOTE_NAME, ex );
            return null;
        }
    }
}

我们之前在JBOSS EAP6上运行,并开始使用CDI进行实验。因此,我们向data-service-beans添加了一个beans.xml,并使用@Produces将(在这种情况下为OrganisationAndMandatesRegister)变成CDI可注入的对象。我们的想法是,在未来,我们可能会重新打包我们的应用程序,并将数据服务与业务服务一起打包到一个企业存档中。
最近,我们迁移到了JBOSS EAP7.2(Wildfly 8?),突然出现了各种意外的延迟和事务问题。
我怀疑我们获取bean的方式是这些问题的因素之一。例如:我猜作用域取决于业务EJB生命周期,但对于每次调用业务服务中的locate(),都会产生一个新的数据服务实例。
那么问题来了:在使用CDI时,生产远程bean(通过RMI)的最佳方法是什么?是否应考虑作用域,因为两种类型的服务均为无状态(或者说这是自动完成的)?
1个回答

4
如果生产者方法没有定义作用域,则使用@Dependend,因此找到适当的作用域,可能是@RequestScoped。当您从JNDI检索EJB时,您不会获得新实例,而是从池中获取一个实例,该实例可能在多个调用上相同。您的问题可能是EJB拦截器,因为如果依赖范围的EJB实例一旦被注入就始终相同,并且永远不会释放。
去掉@Produces,因为CDI与EJB集成,可以通过@Inject或@EJB注入EJB。如果要保留Locator类,则可以在其中注入EJB,并返回正确的EJB实例(实际上是代理),其中Locator应为@ApplicationScoped。另一种方法是使用Instance进行编程查找。使用Object类型,您可以访问容器中的所有CDI Bean(包括EJB),因此共同接口将有助于最小化可访问的Bean。
请参见以下链接以获取更多帮助。

https://docs.jboss.org/weld/reference/latest/en-US/html/injection.html#_obtaining_a_contextual_instance_by_programmatic_lookup

在CDI Weld ManagedBean中使用@Inject注入一个无状态EJB(JSF 1.2 EJB应用程序在jboss 6 AS上)

http://www.adam-bien.com/roller/abien/entry/inject_vs_ejb


简而言之:

选项a)保持不变。也许可以使用@Dependent明确范围,以表明在调用bean创建时调用(在调用bean的构造函数中注入)

选项b)使用无状态的@ApplicationScoped会话bean

@LocalBean // otherwise @EJB will not work
@ApplicationScoped // this instance should be created only once per ear
public class OMRLocator {

    @EJB // does implicitly a remote (default) JNDI lookup
    private OrganisationsAndMandatesRegister instance;

    @Produces
    @Dependent // just to make it explicit
    public OrganisationsAndMandatesRegister locate() {
       return instance;
    }
}

Hertzog,我不想再引入更多的bean了。但是,我对@Produces进行了一些实验。令我惊讶的是(当我调试时),纯粹的@Produces只会导致locate方法被调用一次。然而,当我将@RequestScoped添加到@Produces中时,每个bean调用都会突然调用locate(这正是我首先期望的,因为作用域是依赖于它的)。我还没有理解这个。 - Sjaak
1
DependendScoped Beans每个注入它的bean实例只会被生产一次。它们就像普通实例,没有代理管理它们,它们由获取它们注入的bean所拥有和管理。而RequestScoped beans则在每个请求中被生产和注入。所以应该为每个请求中生产的EJB调用一次locate方法。 - Thomas Herzog
每次调用生产者时,都会创建一个包含具有依赖范围的生产者的类的实例。如果应用程序作用域,则仅创建一次该实例。如果您离开jndi查找,则不应该有问题。如果您通过EJB注释注入EJB并返回引用,则不应该有问题,因为您返回访问ejb容器的实际bean的代理。也许您可以尝试进行调试。 - Thomas Herzog
好的。所以如果我理解正确,@EJB和上面的纯JNDI查找之间有区别吗?(代理与非代理)......这些bean(我有几个)实际上是由@Produces注入的,具有构造函数注入,我更喜欢使用字段(单元测试)。据我所知,@EJB只能用于字段。我试图完全删除定位器,但似乎无法使用@ Inject进行远程EJB(Weld抱怨)...我认为@EJB仅适用于企业bean,因此如果我遵循您的建议,则定位器本身必须是@ApplicationScoped(本地)无状态bean。 - Sjaak
当然,我进行了一些调试。但有时候变量太多了,很难理解你所看到的东西 :). 关于CDI和EJB的在线文档并不总是对这些主题非常简明扼要。再加上一个新版本的应用服务器,很容易迷失方向。这对我非常有帮助。 - Sjaak
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接