将一个EJB注入到JAX-RS(RESTful服务)中

73

我正在尝试通过注释将一个无状态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

你们能展示一些可行的例子吗?

7个回答

115

我不确定这是否应该起作用。所以有两个选项:

选项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 选项3也可以像这样:@EJB private BookEJB bookEJB,这将起作用(GF 3.1.2) - NBW
2
“我不确定这应该起作用。” - 为什么?我认为你只需要注入一个EJB而不需要玩很多游戏。 - Ryan
@Pascal Thivent:第二个选项有什么缺点吗?比如会增加 JBoss 中的 bean 数量吗? - nilesh
只有在将 beans.xml 添加到应用程序中时,选项2和3才对我起作用。 - Christian13467
在WebLogic 12c上:选项1和2都可以工作,但是我无法通过任何注释组合使选项3工作。我有意避免将每个类都变成EJB以使注入工作,因此最终选择了选项1。如果有人能够在WebLogic中获得选项3的工作示例,我很乐意看到它! - Matsu Q.
显示剩余3条评论

15

这个帖子已经有点老了,但是昨天我也遇到了同样的问题。这是我的解决方案:

只需通过@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与ejbs打包在一起,则将其放入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注释,则会有问题,因为@ManagedBean没有部署描述符。 - Laird Nelson
我曾经遇到过同样的问题。我需要beans.xml才能让它运行起来。 我的环境是JBoss EAP 6.4和Jersey REST服务。我遇到了JBAS011859错误。但最终它还是运行起来了。 - Christian13467

10

您可以在JAX-RS资源中进行注入而无需将其作为EJB或CDI组件。但是,您必须记住,您的JAX-RS资源不能是单例。

因此,您可以使用以下代码设置应用程序。这使得BookResource类成为每个请求的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 并注入所有所需的依赖项。如果您将 BookResource 类设置为 singleton JAX-RS 资源,那么您需要将其放在 getSingletons 中。

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

接下来,你创建了一个实例,该实例不受JAX-RS运行时管理,容器中没有人关心注入任何内容。


“singleton” 这个东西和 @EJB 的问题没有关系,对吧? - Jin Kwon
在我的情况下,我的REST资源是单例的。因此,即使我使用@EJB或@Inject注入会话bean,我的bean实例始终为null,导致NullPointerException。你最后关于单例的提示帮助我解决了这个问题。我确保我的REST资源不是单例的。 - user613114
旧帖子,但非常感谢@Martin!我从昨天开始就一直在苦苦挣扎这个问题。你的单例提示解决了它。 - peez80

5
很不幸,我的答案太长了,无法在评论中回复。Zeck,我希望你明确自己正在做什么,将你的bean升级为EJB,就像Pascal所建议的那样。然而,尽管现在使用Java EE“使类成为EJB”变得容易,但你应该意识到这样做的影响。每个EJB都会创建开销以及提供的附加功能:它们是事务感知的,有自己的上下文,参与完整的EJB生命周期等。
我认为你应该采取一个清晰且可重用的方法:将对服务器服务的访问(希望是通过SessionFacade :)访问)提取到BusinessDelegate中。该委托应该使用某种JNDI查找(可能是ServiceLocator-是的,在Java EE中仍然有效!)来访问后端。

好的,私下说:如果你真的、真的、真的需要注入,因为你不想手动编写JNDI访问,你仍然可以将你的代理设为EJB,虽然这样做...感觉有些不对。

这样至少后面要替换成其他东西时会更容易,如果你决定转向JNDI查找方法...


当你没有完全阅读问题时,就会出现这种情况。在您的RESTful服务的情况下,可以说该服务本身就是您的BusinessDelegate。因此,您已经知道了:我的陈述的第一部分和最后一部分仍然完全有效,而我可能不会为BusinessDelegate创建一个BusinessDelegate... :) - LeChe
我也不太喜欢将我的API端点作为EJB,但你可以使用@ManagedBean。如果你愿意,可以看看我的回答。 - Michael Simons
您的JNDI提供程序是在“选项1:使用注入提供程序SPI”示例中开发的。 - Viacheslav Dobromyslov
2
人们经常谈论EJB的“开销”,好像这是一个早已确立的事实,即它们很笨重,但大多数开发人员从未费心去衡量它。Adam Bien在2008年展示了EJB和POJO之间性能差异很小(<10%)(http://www.adam-bien.com/roller/abien/entry/is_it_worth_using_pojos) ,而且性能自那时以来只有提高。简而言之,如果您试图通过比较POJO和EJB来优化应用程序的性能,那么您正在错误的地方寻找。 - DavidS
@DavidS:这里有几个要点:首先,10%是巨大的!即使现在只有5或2%,也没有逻辑上的理由接受这种影响:这是一种易于获得性能提升而没有任何缺点的方法。此外,正如您所知,性能降级是非线性的。因此,在构建系统时记住这一点是有意义的。最后,虽然我必须承认我的回答强调了性能部分,但我认为还有其他可能更重要的原因不将所有内容都变成EJB,例如代码的简单性和具有清晰的依赖关系链。 - LeChe
1
我认为你误解了那个10%的数字,LeChe。它不是“我的应用现在变慢了10%”,而是应用程序中一个特定类型的操作变慢了10%。使用EJB而不是POJO并不会使网络、数据库、文件IO和渲染需要更多的时间,而这些操作构成了许多系统的主体。在真实应用程序中使用EJB与POJO相比的整体性能影响可能是可以忽略不计的。至于你评论的后半部分,我同意有很好的理由不将所有内容都变成EJB:幸运的是,Pascal提出了两种替代方案。 - DavidS

3

我正试图做完全相同的事情。我正在使用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查找技巧

我确实使用了这种方法,而且它确实起作用了。然而,同样的代码在jboss中也能正常工作...所以我真的不确定为什么会出现这种情况... - mwangi
我不得不在一个旧应用程序中使用这个。然而,在jboss 6.4中,我发现只有完整的jndi名称才能工作:java:global/completeEar/completeEar-ejb-1.0-SNAPSHOT/TestEJBImpl!personal.ejb.TestEJBRemote。 - cabaji99

2

Arjan是正确的。我创建了另一个类来初始化EJB,而不是为RS创建一个bean。

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

为避免空指针,请使用:

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

它实际上在GF上运行


0

我有同样的问题,我通过上下文查找调用EJB来解决它(注入是不可能的,我遇到了相同的空指针异常错误)。


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