14

我很喜欢学习 JAX-RS 和 Jersey,但是我在尝试测试需要注入 DAO 的简单资源时遇到了障碍,如下所示:

@Stateless
@Path("simple")
public class SimpleResource {

    @PersistenceContext
    private EntityManager em;

// @GET, etc...

}

(我将转向更抽象的 DAO 模式,但问题是一样的,即如何注入 @EJB DAO?)

在我的单元测试中,我使用的是在其 web.xml 中配置 Jersey 的嵌入式 Jetty 服务器,并且我想挂钩资源的生命周期,以便我可以注入一个模拟 EntityManager,但我还没有找到一个干净的答案经过大量搜索。你能帮我吗?我遇到的一些可能的方向:

1) 在我的代码中使用 JNDI 上下文查找来获取 DAO bean,并在测试中注册模拟对象。

而不是@EJB 或@PersistenceContext,在资源的构造函数中使用类似这样的东西:

  theDAO = (DAOImpl) new InitialContext().lookup("java:global/EJB/DAOImpl");

但是,这意味着我的测试环境需要支持 JNDI,而在 Jetty 中这样做可能会带来一些痛苦。另外,它不使用干净的注释方法。

2)使用方法注入。

注入该方法,以便我可以设置 DAO 后实例化,例如,

@PersistenceContext(name = "persistence/pu00")
public void setPersistenceUnit00(final EntityManager em) {
    em00 = em;
}

或者

private MyEjbInterface myEjb;
@EJB(mappedName="ejb/MyEjb")
public void setMyEjb(MyEjb myEjb) {
    this.myEjb = myEjb;
}

但是,要做到这一点,我需要 Jersey 实例化的实例,例如 SimpleResource。我怎么得到它?

3)使用反射。

一种 DIY 注射,类似于:

public static void setPrivateField(Class<? extends Object> instanceFieldClass, Object instance, String fieldName, Object fieldValue) {
    Field setId = instanceFieldClass.getDeclaredField(fieldName);
    setId.setAccessible(true);
    setId.set(instance, fieldValue);
}

同样,我需要 Jersey 实例化的实例。

4) 使用注入提供程序。

我仍然粗略地了解它是如何工作的,但看起来 Jersey 提供了一种定义自定义可注射注释的方法,例如,

    @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;
    }
    }
}

使用辅助类的变体:

Server server = new Server(8080);
Context root = new Context(server,"/",Context.SESSIONS);

ResourceConfig rc = new PackagesResourceConfig("edu.mit.senseable.livesingapore.platform.restws.representations");
rc.getSingletons().add(new SingletonTypeInjectableProvider<javax.ws.rs.core.Context, Myobj>(Myobj.class, new Myobj(12,13)){});

root.addServlet(new ServletHolder(new ServletContainer(rc)), "/");
server.start();

有了这个用途:

@Path("/helloworld")
public class HelloWorldResource {
    @Context Myobj myClass;
    ....
}

这对@EJB 或@PersistenceContext 是否可行?

5) 扩展 javax.ws.rs.core.Application。

对此粗略,但是:

@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;
  }
}

6)扩展ServletContainer。

使用 InjectableProvider 的旧风格?看起来更复杂:

public class ServletAdapter extends ServletContainer {

@Override
protected void configure(ServletConfig servletConfig, ResourceConfig rc, WebApplication wa) {
    super.configure(servletConfig, rc, wa);

    rc.getSingletons().add(new InjectableProvider<Resource, Type>() {

        public ComponentScope getScope() {
            return ComponentScope.Singleton;
        }

        public Injectable<Object> getInjectable(ComponentContext ic, Resource r, Type c) {
            final Holder value = new Holder();

                Context ctx = new InitialContext();
                try {
                    value.value = ctx.lookup(r.name());
                } catch (NamingException ex) {

                    value.value = ctx.lookup("java:comp/env/" + r.name());
                }

            return new Injectable<Object>() {
                public Object getValue() {
                    return value.value;
                }
            };
        }
    });
}
}  

7) 使用嵌入式 EJB 容器。

例如, http: //openejb.apache.org。这很重,我预计开始工作会很混乱。(事实上​​,让我开始走“Jetty + Jersey”路线的是 GlassFish Embedded 中围绕安全登录的一个错误。我还查看了其他 Java EE 6 应用程序容器,如 JBoss AS,但每个在嵌入式模式下都有问题,用户有限社区支持。)

8) 使用第三方 IoC 库,如 Spring 或 Guice。

Spring 显然通常用于解决这类问题(在单元测试时注入模拟),但我想避免不得不学习另一组 API ——纯 Java EE 已经足够挑战了!但如果这是最好的解决方案,我很乐意。我还没有仔细研究过 Spring 或 Guice。

你成功使用过这些吗?您喜欢其他任何解决方案吗?我真的很期待您对此的建议。提前致谢——马特

4

2 回答 2

1

由于您使用的是 Netbeans,请尝试一下:

使用嵌入式 EJB 容器测试企业应用程序

本教程使用嵌入式 Glassfish 容器并注入一个封装 EntityManager 的 EJB(类似于您在第一个选项中描述的内容)。

于 2013-02-01T08:38:28.833 回答
0

如果您只需要一个EntityManager内嵌的 Jetty 容器,为什么首先要使用注入?您可以将其中一个 JPA 实现(例如 eclipselink 或 hibernate)放在您的类路径中,配置一个资源本地持久性单元,然后像这样获取它:

EntityManagerFactory emf = Persistence.createEntityManagerFactory("your unit name");
EntityManager em = emf.createEntityManager();

@EJB为了测试您的 JAX-RS 类,拥有一些行为类似于您的东西(可能是静态 DAO 工厂?)就足够了。

如果您确实希望您的单元测试尽可能接近 Java EE 环境,请考虑使用 Arquillian (http://www.jboss.org/arquillian.html) 运行它们。它直接在 Java EE 容器上运行测试——这很简单,它有很好的文档。

于 2012-11-02T14:08:47.180 回答