How to enable CDI inject in web service (jaxrs/jersey) on java se running grizzly?
Asked Answered
U

3

13

How do I allow CDI injection of resources into restful web service resources? I am running on standard java using weld 2 (cdi), jersey (jaxrs), and grizzly (web server). Here is my simple web resource:

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();
  }
}

And here is how I've got weld starting my simple web server:

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();
    }
  }
}

And the code that I suspect will need to be modified to inform jersey to use weld for CDI inject resolution:

...
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();
  }
}
Ukase answered 20/6, 2013 at 21:38 Comment(0)
U
13

After seeing this stackoverflow post, I implemented the following solution. Not sure if it is the best route to take, but it worked.

I created an hk2 Binder and registered the Binder:

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

Then modified the ResourceConfig instantiation from above to:

final ResourceConfig resourceConfig = new ResourceConfig()
    .packages("training.webservice")
    .register(new JacksonFeature())
    .register(new WebServiceBinder());
Ukase answered 20/6, 2013 at 22:44 Comment(9)
That will work, but you're better off to use the SE integration of the CDI implementation you're using (it's not in the spec, but they all do it).Dahlberg
Do you mean instead of initializing CDI via the Weld object I should be using (in the case of Weld) the StartMain.main static method and then have a public void main(@Observes ContainerInitialized event)? or are you referring to the hk2 CDI implementation?Ukase
Either one will work. If you're in an OSGI environment and need the separation the hk2 or weld-osgi project would work.Dahlberg
Could you make it work using the ContainerInitialized approach?Tavi
BTW, a better way to obtain the BeanManager is CDI.current().getBeanManager().Tavi
@Tavi Thanks for the information... and no, I didn't end up observing the ContainerInitialized event.Ukase
I used your approach too (save for the part where we get the BeanManager reference), and it worked. However, like you, I feel this is not optimal. I searched everywhere, and I couldn't find a solution to integrate Grizzly's H2K with a CDI implementation. Too bad.Tavi
OMG, you guys saved my life. Using the bean manager for the injection binding is the only way I got my @ApplicationScoped CDI beans (Weld 2.0.4) injected in my JAX-RS resource class (Jersey 2.3.1). But I wonder why it is so complicated getting CDI and JAX-RS work together in a servlet container?Franchescafranchise
Warning. This answer is outdated since Jersey 2.Strongminded
N
6

The selected answer dates from a while back. It is not practical to declare every binding in a custom HK2 binder. I just had to add one dependency. Even though it was designed for Glassfish it fits perfectly into other containers. I'm using Tomcat / Grizzly.

   <dependency>
        <groupId>org.glassfish.jersey.containers.glassfish</groupId>
        <artifactId>jersey-gf-cdi</artifactId>
        <version>2.14</version>
    </dependency>

Here is an example with JerseyTest (same principle if you run it from a main method). I just had to declare a dependency on weld-se and declare a Weld container before instantiating my resources - as you also did - and it works out of the box.

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();
    }
}
Napalm answered 8/1, 2015 at 20:30 Comment(3)
True that accepted answer is outdated since when Jersey 2 has seen the daylight but ... your code snippet is also flawed: new Weld() should be used to start Weld container in a standalone env, not inside the servlet container. Register Weld servlet listener instead.Strongminded
Could you then post a working example registering a Weld servlet listener on a JerseyTest config ?Napalm
Don't get me wrong, your code snippet is perfectly ok, as it is just a test class. But OP asked about enabling CDI (Weld) in a servlet container.Strongminded
S
2

Since at least Weld 2.2.0.Final there is no need to mess up with HK2 Binder.

As official Weld documentation states you just need to register org.jboss.weld.environment.servlet.Listener. Code snipped from doc:

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

Above servlet listener manages the whole lifecycle of the Weld container. So there is no need to:

 Weld weld = new Weld();
 WeldContainer container = weld.initialize();

UPDATE As @EdMelo pointed out, Grizzly HTTP server is not a fully compliant Servlet container. I didn't know this, thanks for this hint. So I'm not sure, if my answer still applies here.

Strongminded answered 3/8, 2015 at 10:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.