如何在运行Grizzly的Java SE上启用Web服务(JAX-RS / Jersey)中的CDI注入?

13

如何在RESTful Web服务资源中允许CDI注入资源? 我正在使用Weld 2(CDI)、Jersey(JAX-RS)和Grizzly(Web服务器)运行标准Java。这是我的简单Web资源:

import training.student.StudentRepository;
import javax.inject.Inject;
import javax.ws.rs.*;

@Path("student")
public class StudentWebResource {
  @Inject
  private StudentRepository studentRepository;  

  @GET
  @Path("count")
  @Produces(MediaType.TEXT_PLAIN)
  public Integer getCount() {
    return studentRepository.studentCount();
  }
}

以下是我如何使用weld启动我的简单Web服务器:

public class Main {
  public static void main(String[] args) throws Exception {
    startCdiApplication();
  }

  public static void startCdiApplication() throws Exception {
    Weld weld = new Weld();
    try {
      WeldContainer container = weld.initialize();
      Application application = container.instance().select(WebServer.class).get();
      application.run();
    } 
    finally {
      weld.shutdown();
    }
  }
}

我怀疑需要修改的代码是通知jersey使用weld进行CDI注入解析的代码:

...
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;

public class WebServer implements Application {

  /*
   * startup the grizzly http server to make available the restful web services
   */
  private void startWebServer() throws IOException, InterruptedException {
    final ResourceConfig resourceConfig = new ResourceConfig().packages("training.webservice").register(new JacksonFeature());
    final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(getBaseUri(), resourceConfig);
    server.start();
    Thread.currentThread().join();
  }

  ...

  @Override
  public void run() throws IOException, InterruptedException {
    startWebServer();
  }
}
3个回答

13

看完这篇stackoverflow帖子后,我实现了以下解决方案。不确定是否是最佳选择,但它起作用了。

我创建了一个hk2绑定器并注册了该绑定器:

public class WebServiceBinder extends AbstractBinder {

  @Override
  protected void configure() {
    BeanManager bm = getBeanManager();
    bind(getBean(bm, StudentRepository.class))
        .to(StudentRepository.class);
  }

  private BeanManager getBeanManager() {
    // is there a better way to get the bean manager?
    return new Weld().getBeanManager();
  }

  private <T> T getBean(BeanManager bm, Class<T> clazz) {
    Bean<T> bean = (Bean<T>) bm.getBeans(clazz).iterator().next();
    CreationalContext<T> ctx = bm.createCreationalContext(bean);
    return (T) bm.getReference(bean, clazz, ctx); 
  }
}

然后,将上述ResourceConfig实例化进行修改为:
final ResourceConfig resourceConfig = new ResourceConfig()
    .packages("training.webservice")
    .register(new JacksonFeature())
    .register(new WebServiceBinder());

3
顺便说一下,更好的获取BeanManager的方法是CDI.current().getBeanManager()。 - javabeats
@javabeats 感谢提供的信息... 不过,我最终没有观察到 ContainerInitialized 事件。 - Brice Roncace
我也使用了你的方法(除了获取BeanManager引用的部分),它确实有效。然而,像你一样,我觉得这不是最佳方案。我到处搜索,但找不到将Grizzly的H2K与CDI实现集成的解决方案。太遗憾了。 - javabeats
哇,你们救了我的命。使用bean管理器进行注入绑定是我得到@ApplicationScoped CDI Bean(Weld 2.0.4)在我的JAX-RS资源类(Jersey 2.3.1)中被注入的唯一方式。但我想知道为什么在servlet容器中让CDI和JAX-RS一起工作这么复杂呢? - Sebastian S.
1
警告:自Jersey 2以来,此答案已过时。 - G. Demecki
显示剩余4条评论

6
所选答案来自一段时间以前。在自定义HK2绑定程序中声明每个绑定是不实际的。 我只需要添加一个依赖项。即使它是为Glassfish设计的,它也完美地适用于其他容器。我正在使用Tomcat / Grizzly。
   <dependency>
        <groupId>org.glassfish.jersey.containers.glassfish</groupId>
        <artifactId>jersey-gf-cdi</artifactId>
        <version>2.14</version>
    </dependency>

以下是使用JerseyTest的示例(如果您从主方法运行,原理相同)。我只需要声明一个weld-se依赖项,并在实例化资源之前声明一个Weld容器 - 就像您做的那样 - 它可以开箱即用。

public class GrizzlyTest extends JerseyTest {
    private Weld weld;
    private WeldContainer container;

    @Override
    protected Application configure() {
        weld = new Weld();
        container = weld.initialize();
        return new ResourceConfig(MyResource.class);
    }

    @Test
    public void test() {
        System.out.println(target("myresource").request().get(String.class));
    }

    @After
    public void after() {
        weld.shutdown();
    }
}

虽然 Jersey 2已经问世,但是接受的答案已经过时了,但是...你的代码片段也有缺陷:在独立环境中应该使用new Weld()来启动Weld容器,而不是在servlet容器内部。应该注册Weld servlet监听器。 - G. Demecki
你能否提供一个可工作的示例,演示如何在JerseyTest配置中注册Weld servlet监听器? - Olivier Tonglet
不要误会,你的代码片段完全没问题,因为它只是一个测试类。但是 OP 问到如何在 Servlet 容器中启用 CDI(Weld)。 - G. Demecki

2

自Weld 2.2.0.Final起,无需与HK2 Binder混淆。

正如官方Weld文档所述,您只需要注册org.jboss.weld.environment.servlet.Listener。文档中的代码片段:

public class Main {
    public static void main(String[] args) throws ServletException, LifecycleException {
        Tomcat tomcat = new Tomcat();
        Context ctx = tomcat.addContext("/", new File("src/main/resources").getAbsolutePath());

        Tomcat.addServlet(ctx, "hello", HelloWorldServlet.class.getName());
        ctx.addServletMapping("/*", "hello");

        ctx.addApplicationListener(Listener.class.getName());

        tomcat.start();
        tomcat.getServer().await();
    }

    public static class HelloWorldServlet extends HttpServlet {
        @Inject
        private BeanManager manager;

        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.setContentType("text/plain");
            resp.getWriter().append("Hello from " + manager);
        }
    }
}

上述Servlet监听器管理Weld容器的整个生命周期。因此,不需要进行以下操作:
 Weld weld = new Weld();
 WeldContainer container = weld.initialize();

更新 正如@EdMelo所指出的那样,Grizzly HTTP服务器不是一个完全符合Servlet容器的标准。我不知道这一点,感谢他的提示。因此,我不确定我的答案是否仍然适用于此处。


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