将 EJB 注入 JAX-RS(RESTful 服务)

Posted

技术标签:

【中文标题】将 EJB 注入 JAX-RS(RESTful 服务)【英文标题】:Inject an EJB into JAX-RS (RESTful service) 【发布时间】:2011-03-02 22:52:41 【问题描述】:

我正在尝试通过注释将 Stateless EJB 注入到我的 JAX-RS Web 服务中。不幸的是,EJB 只是 null,当我尝试使用它时,我得到了 NullPointerException

@Path("book")
public class BookResource 

    @EJB
    private BookEJB bookEJB;

    public BookResource() 
    

    @GET
    @Produces("application/xml")
    @Path("/bookId")
    public Book getBookById(@PathParam("bookId") Integer id)
    
        return bookEJB.findById(id);
    

我做错了什么?

以下是关于我的机器的一些信息:

Glassfish 3.1 Netbeans 6.9 RC 2 Java EE 6

你们能展示一些工作示例吗?

【问题讨论】:

【参考方案1】:

我不确定这是否应该有效。所以要么:

选项 1:使用注入提供程序 SPI

实现一个提供者来进行查找和注入 EJB。见:

@EJB injection。

com.sun.jersey:jersey-server:1.17 的示例:

import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;

import javax.ejb.EJB;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.ws.rs.ext.Provider;
import java.lang.reflect.Type;

/**
 * JAX-RS EJB Injection provider.
 */
@Provider
public class EJBProvider implements InjectableProvider<EJB, Type> 

    public ComponentScope getScope() 
        return ComponentScope.Singleton;
    

    public Injectable getInjectable(ComponentContext cc, EJB ejb, Type t) 
        if (!(t instanceof Class)) return null;

        try 
            Class c = (Class)t;
            Context ic = new InitialContext();

            final Object o = ic.lookup(c.getName());

            return new Injectable<Object>() 
                public Object getValue() 
                    return o;
                
            ;
         catch (Exception e) 
            e.printStackTrace();
            return null;
        
    

选项 2:使 BookResource 成为 EJB

@Stateless
@Path("book")
public class BookResource 

    @EJB
    private BookEJB bookEJB;

    //...

见:

How to Combine REST Services with EJB 3.1 EJB 3.1 And REST - The Lightweight Hybrid

选项 3:使用 CDI

@Path("book")
@RequestScoped
public class BookResource 

    @Inject
    private BookEJB bookEJB;

    //...

见:

Injecting an EJB from a jar into a jax-rs class in a war

【讨论】:

亲爱的@Pascal Thivent 将 JAX-Resources 作为@Stateless 是唯一的出路吗? @Pascal 选项 3 也可以如下所示:@EJB private BookEJB bookEJB 并且它可以工作 (GF 3.1.2) “我不确定这是否可行。” - 为什么?我希望你可以在不玩一堆游戏的情况下注入一个 EJB @Ryan:不,这一点都不容易,因为 Glassfish 4.1 HK2 存在错误。我今天遇到了同样的问题。 @Pascal Thivent:第二个选项有什么缺点吗?喜欢..它会增加jboss中的bean吗?【参考方案2】:

很遗憾,我的回答太长,无法发表评论,所以就这样吧。 :)

Zeck,我希望您了解通过将 bean 升级为 EJB 所做的具体操作,正如 Pascal 所建议的那样。不幸的是,就像现在使用 Java EE '使一个类成为 EJB' 一样容易,您应该意识到这样做的含义。每个 EJB 都会产生开销以及它提供的附加功能:它们是事务感知的、有自己的上下文、它们参与整个 EJB 生命周期等。

我认为你应该为一个干净和可重用的方法做的是:提取对你的服务器服务的访问(希望通过SessionFacade :) 进入BusinessDelegate。此委托应该使用某种 JNDI 查找(可能是 ServiceLocator - 是的,它们在 Java EE 中仍然有效!)来访问您的后端。

好吧,不记录了:如果你真的,真的,真的需要注入,因为你不想手动编写 JNDI 访问,你仍然可以让你的委托成为 EJB,尽管它.. ……嗯,就是感觉不对。 :) 这样,如果您决定切换到 JNDI 查找方法,至少以后可以很容易地用其他东西替换它......

【讨论】:

Errr,这就是您没有完整阅读问题时得到的结果。对于您的 RESTful 服务,可以说,服务本身就是您的 BusinessDelegate。所以你有它:我声明的第一部分和最后一部分仍然完全有效,而我可能不会为 BusinessDelegate 创建一个 BusinessDelegate ... :) 我也不愿意让我的 api 端点 EJB,但你可以只使用 @ManagedBean。喜欢的话可以看我的回答 您提供的 JNDI 是在Option 1: Use the injection provider SPI 示例中开发的。 人们经常谈论 EJB 的“开销”,好像它们是重量级的已经是公认的事实,但大多数开发人员从来没有费心去衡量它。 Adam Bien 表明 EJB 和 POJO 之间的性能差异在 2008 年 (adam-bien.com/roller/abien/entry/is_it_worth_using_pojos) 很小 ( 我认为你误解了 10% 的数字,乐车。这不是“我的应用程序现在慢了 10%”,而是应用程序中的一种特定类型的操作慢了 10%。使用 EJB 代替 POJO 不会使网络、数据库、文件 IO 和渲染花费 10% 以上的时间,而这些操作构成了许多系统的主体。在实际应用程序中使用 EJB 与 POJO 的总体性能影响可能可以忽略不计。至于您评论的后半部分,我同意有充分的理由不将所有内容都设为 EJB:幸运的是,Pascal 提出了两个替代方案。【参考方案3】:

您应该能够在 JAX-RS 资源中进行注入,而无需使其成为 EJB 或 CDI 组件。但是您必须记住,您的 JAX-RS 资源不能是单例的。

因此,您使用此代码设置您的应用程序。这使得 BookResourceper-request JAX-RS 资源。

@javax.ws.rs.ApplicationPath("application")
public class InjectionApplication extends javax.ws.rs.core.Application 
  private Set<Object> singletons = new HashSet<Object>();
  private Set<Class<?>> classes = new HashSet<Class<?>>();

  public InjectionApplication() 
    // no instance is created, just class is listed
    classes.add(BookResource.class);
  

  @Override
  public Set<Class<?>> getClasses() 
    return classes;
  

  @Override
  public Set<Object> getSingletons() 
    return singletons;
  

通过此设置,您可以让 JAX-RS 根据每个请求为您实例化 BookResource 并注入所有必需的依赖项。如果你制作 BookResourcesingleton JAX-RS 资源,这就是你放入 getSingletons

public Set<Object> getSingletons() 
  singletons.add(new BookResource());
  return singletons;

然后,您创建了不受 JAX-RS 运行时管理的实例,并且容器中没有人关心注入任何东西。

【讨论】:

“单例”与@EJB 问题无关,对吧? 就我而言,我的 REST 资源是单例的。所以即使我注入会话bean(使用@EJB 或@Inject),通过bean 实例总是为空,导致NullPointerException。您关于单身人士的最后提示帮助我解决了这个问题。我确保我的 REST 资源不是单例的。 旧帖子,但非常感谢@Martin!从昨天开始,我一直在努力解决这个问题。您对单例的提示成功了。【参考方案4】:

我有同样的问题,我通过上下文查找调用 te EJB 解决了它(注入是不可能的,我有同样的错误 NullPointerException)。

【讨论】:

【参考方案5】:

这个帖子比较老了,不过我昨天也遇到了同样的问题。这是我的解决方案:

只需在类级别通过@javax.annotation.ManagedBean 使 BookResource 成为托管 bean。

为此,您需要使用 beans.xml 启用 CDI:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

如果 BookResource 是 war 文件的一部分,则此文件需要位于 WEB-INF 中。如果 BookResource 与 ejb 打包在一起,则将其放入 META-INF。

如果你想使用@EJB,你就完成了。如果你想通过@Inject 注入 EJB,那么也必须将 beans.xml 放入 ejbs jar 文件到 META-INF 中。

你在做什么:你只是告诉容器资源应该由容器管理。因此它支持注入以及生命周期事件。因此,您无需将其提升为 EJB 即可拥有业务外观。

您无需扩展 javax.ws.rs.core.Application 即可使用。 BookResource 是作为根资源自动请求作用域的。

使用 Glassfish 3.1.2 和 maven 项目进行测试。

编码愉快。

【讨论】:

我知道这个答案非常陈旧,但是:META-INF/beans.xml 与 Java EE @ManagedBean 注释没有任何关系;你不应该需要它。我也使用@ManagedBean 让我的资源类允许其中的@EJB 引用并且不需要META-INF/beans.xml 参与。同样值得注意的是,如果你这样做了,如果你想在部署描述符中覆盖 @EJB 注释,你会感到很麻烦,因为@ManagedBeans 没有注释。 我遇到了同样的问题。我需要 beans.xml 让它运行。我的环境是带有球衣休息服务的 JBoss EAP 6.4。我遇到了JBAS011859。但最后它运行了。【参考方案6】:

我试图做同样的事情。我正在使用 EJB 3.1 并将我的应用程序部署为具有单独 EJB 项目的 EAR。正如 Jav_Rock 指出的那样,我使用上下文查找。

@Path("book")
public class BookResource 

  @EJB
  BookEJB bookEJB;

  public BookResource() 
    try 
        String lookupName = "java:global/my_app/my_ejb_module/BookEJB";
        bookEJB = (BookEJB) InitialContext.doLookup(lookupName);
     catch (NamingException e) 
        e.printStackTrace();
    
  

  @GET
  @Produces("application/xml")
  @Path("/bookId")
  public Book getBookById(@PathParam("bookId") Integer id) 
    return bookEJB.findById(id);
  

有关非常有用的 JNDI 查找技巧,请参阅下面的链接

JNDI look up tips

【讨论】:

我确实使用了这种方法并且它确实有效。但是,相同的代码在 jboss 中运行良好...所以我真的不确定为什么会发生这种情况.. 我不得不将它用于旧版应用程序。然而在 jboss 6.4 中,我发现只有完整的 jndi 名称有效:java:global/completeEar/completeEar-ejb-1.0-SNAPSHOT/TestEJBImpl!personal.ejb.TestEJBRemote【参考方案7】:

阿扬是对的。我创建了另一个类来初始化 EJB,而不是为 RS 创建一个 bean

@Singleton
@LocalBean
public class Mediator 
    @EJB
    DatabaseInterface databaseFacade;

为了避免空指针:

@Path("stock")
public class StockResource 
    @EJB
    DatabaseInterface databaseFacade;
...

它实际上适用于 GF

【讨论】:

以上是关于将 EJB 注入 JAX-RS(RESTful 服务)的主要内容,如果未能解决你的问题,请参考以下文章

jax-rs RESTful 服务的 spring 安全配置

使用 jaxb、jax-rs 和 ejb 找到正确的架构

RESTful API / MVC 应用程序的 Grails 与 JAX-RS

会话上下文中的 ejb 始终为空

使用JAX-RS创建RESTful Web Service

Java基于JAX-RS开发Restful接口总结