【问题标题】:Inject an EJB into JAX-RS (RESTful service)将 EJB 注入 JAX-RS(RESTful 服务)
【发布时间】: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

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

【问题讨论】:

    标签: java rest jakarta-ee ejb jax-rs


    【解决方案1】:

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

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

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

    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;
    
        //...
    }
    

    见:

    选项 3:使用 CDI

    @Path("book")
    @RequestScoped
    public class BookResource {
    
        @Inject
        private BookEJB bookEJB;
    
        //...
    }
    

    见:

    【讨论】:

    • 亲爱的@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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-06-30
        • 2019-02-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-26
        相关资源
        最近更新 更多