jersey
  1. jersey
  2. JERSEY-1950

Enable Jersey/Guice integration via HK2 Guice Bridge

    Details

    • Type: Improvement Improvement
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: 2.0
    • Fix Version/s: None
    • Component/s: extensions
    • Labels:
      None

      Description

      The HK2 Guice Bridge is available, but there may be more work needed in Jersey in order to enable it.

        Issue Links

          Activity

          Hide
          jhesse added a comment -

          I've recently ported some non-trivial jersey 1.x + guice code to jersey 2, and found what seems like a workable solution. Apart from a couple exceptions, I don't have to think about HK2 at all and can just use all the existing guice based code.

          I define a JerseyModule for my apps:

          public class JerseyModule extends AbstractModule {
            private final Supplier<Injector> injectorSupplier;
          
            public JerseyModule(Supplier<Injector> injectorSupplier) {
              this.injectorSupplier = Preconditions.checkNotNull(injectorSupplier);
            }
          
            @Override
            protected void configure() {
              bind(InjectorContainer.class).toInstance(new InjectorContainer(injectorSupplier));
              bind(ServiceLocator.class).toProvider(ServiceLocatorContainer.class).in(Singleton.class);
            }
          
            @Provides
            public SecurityContext securityContext(ServiceLocator locator) {
              return locator.getService(SecurityContext.class);
            }
          
            @Provides
            public UriInfo uriInfo(ServiceLocator locator) {
              return locator.getService(UriInfo.class);
            }
          }
          

          This module defines the custom components and provides an easy way to add Jersey bindings to guice that are resolvable at injector creation time (by delegating to the service locator). The SecurityContext and UriInfo bindings are the only ones that I needed to port our existing code.

          The ServiceLocatorContainer is simple:

          @Singleton
          public class ServiceLocatorContainer implements Provider<ServiceLocator> {
          
            private volatile ServiceLocator locator;
          
            @Override
            public ServiceLocator get() {
              if (locator == null) {
                throw new IllegalStateException("ServiceLocator not set");
              }
              return locator;
            }
          
            public void setServiceLocator(ServiceLocator locator) {
              this.locator = locator;
            }
          }
          

          As is the InjectorContainer:

          public class InjectorContainer {
          
            private final Supplier<Injector> injectorSupplier;
          
            public InjectorContainer(Supplier<Injector> injectorSupplier) {
              this.injectorSupplier = injectorSupplier;
            }
          
            public Injector getInjector() {
              return Preconditions.checkNotNull(injectorSupplier.get(), "Null injector");
            }
          }
          

          Then, finally, there is a JerseyServletContainer that binds the guice bridge and pieces everything together:

          @Singleton
          @SuppressWarnings("serial")
          public class JerseyServletContainer extends ServletContainer {
          
            private final InjectorContainer injectorContainer;
            private final ServiceLocatorContainer serviceLocatorContainer;
          
            @Inject
            public JerseyServletContainer(ResourceConfig resourceConfig, InjectorContainer injectorContainer,
                ServiceLocatorContainer serviceLocatorContainer) {
              super(resourceConfig);
              this.injectorContainer = checkNotNull(injectorContainer);
              this.serviceLocatorContainer = checkNotNull(serviceLocatorContainer);
            }
          
            @Override
            public void init() throws ServletException {
              super.init();
              setupGuiceBridge();
            }
          
            @Override
            public void reload(ResourceConfig configuration) {
              throw new IllegalStateException("Reload not supported!");
            }
          
            void setupGuiceBridge() {
              Injector injector = injectorContainer.getInjector();
              GuiceBridge gb = GuiceBridge.getGuiceBridge();
              ServiceLocator locator = getApplicationHandler().getServiceLocator();
              // set the service locator so it's available to the guice provider
              serviceLocatorContainer.setServiceLocator(locator);
              gb.initializeGuiceBridge(locator);
          
              GuiceIntoHK2Bridge guiceBridge = locator.getService(GuiceIntoHK2Bridge.class);
              guiceBridge.bridgeGuiceInjector(injector);
            }
          }
          

          Then you just have to pass an injector supplier to the JerseyModule, which is pretty straight forward, eg:

          ...
          AtomicReference<Injector> inj = new AtomicReference();
          List<Module> modules = new ArrayList<>();
          modules.add(new JerseyModule(() -> inj.get()));
          ...
          inj.set(Guice.createInjector(modules));
          ...
          

          I chose not to support the reload() method in the JerseyServletContainer, but it seems possible to do so.

          Everything should just work for an existing Jersey app that relies on guice if you follow a few rules:

          1) Use javax.inject.* (instead of com.google.*) for all injection annotations in resource classes
          2) For non-singleton resources, you have to add the @PerLookup annotation to the resource since HK2 uses singleton by default
          3) Don't bind resource classes in guice modules, they'll be created by HK2 (however with the above code, you are expected to bind a ResourceConfig, which is where you register your resource classes for HK2)

          That's pretty much it. If you need additional jersey bindings to be available to guice at module creation time, just create new ones that delegate to the ServiceLocator again.

          Show
          jhesse added a comment - I've recently ported some non-trivial jersey 1.x + guice code to jersey 2, and found what seems like a workable solution. Apart from a couple exceptions, I don't have to think about HK2 at all and can just use all the existing guice based code. I define a JerseyModule for my apps: public class JerseyModule extends AbstractModule { private final Supplier<Injector> injectorSupplier; public JerseyModule(Supplier<Injector> injectorSupplier) { this .injectorSupplier = Preconditions.checkNotNull(injectorSupplier); } @Override protected void configure() { bind(InjectorContainer.class).toInstance( new InjectorContainer(injectorSupplier)); bind(ServiceLocator.class).toProvider(ServiceLocatorContainer.class).in(Singleton.class); } @Provides public SecurityContext securityContext(ServiceLocator locator) { return locator.getService(SecurityContext.class); } @Provides public UriInfo uriInfo(ServiceLocator locator) { return locator.getService(UriInfo.class); } } This module defines the custom components and provides an easy way to add Jersey bindings to guice that are resolvable at injector creation time (by delegating to the service locator). The SecurityContext and UriInfo bindings are the only ones that I needed to port our existing code. The ServiceLocatorContainer is simple: @Singleton public class ServiceLocatorContainer implements Provider<ServiceLocator> { private volatile ServiceLocator locator; @Override public ServiceLocator get() { if (locator == null ) { throw new IllegalStateException( "ServiceLocator not set" ); } return locator; } public void setServiceLocator(ServiceLocator locator) { this .locator = locator; } } As is the InjectorContainer: public class InjectorContainer { private final Supplier<Injector> injectorSupplier; public InjectorContainer(Supplier<Injector> injectorSupplier) { this .injectorSupplier = injectorSupplier; } public Injector getInjector() { return Preconditions.checkNotNull(injectorSupplier.get(), "Null injector" ); } } Then, finally, there is a JerseyServletContainer that binds the guice bridge and pieces everything together: @Singleton @SuppressWarnings( "serial" ) public class JerseyServletContainer extends ServletContainer { private final InjectorContainer injectorContainer; private final ServiceLocatorContainer serviceLocatorContainer; @Inject public JerseyServletContainer(ResourceConfig resourceConfig, InjectorContainer injectorContainer, ServiceLocatorContainer serviceLocatorContainer) { super (resourceConfig); this .injectorContainer = checkNotNull(injectorContainer); this .serviceLocatorContainer = checkNotNull(serviceLocatorContainer); } @Override public void init() throws ServletException { super .init(); setupGuiceBridge(); } @Override public void reload(ResourceConfig configuration) { throw new IllegalStateException( "Reload not supported!" ); } void setupGuiceBridge() { Injector injector = injectorContainer.getInjector(); GuiceBridge gb = GuiceBridge.getGuiceBridge(); ServiceLocator locator = getApplicationHandler().getServiceLocator(); // set the service locator so it's available to the guice provider serviceLocatorContainer.setServiceLocator(locator); gb.initializeGuiceBridge(locator); GuiceIntoHK2Bridge guiceBridge = locator.getService(GuiceIntoHK2Bridge.class); guiceBridge.bridgeGuiceInjector(injector); } } Then you just have to pass an injector supplier to the JerseyModule, which is pretty straight forward, eg: ... AtomicReference<Injector> inj = new AtomicReference(); List<Module> modules = new ArrayList<>(); modules.add( new JerseyModule(() -> inj.get())); ... inj.set(Guice.createInjector(modules)); ... I chose not to support the reload() method in the JerseyServletContainer, but it seems possible to do so. Everything should just work for an existing Jersey app that relies on guice if you follow a few rules: 1) Use javax.inject.* (instead of com.google.*) for all injection annotations in resource classes 2) For non-singleton resources, you have to add the @PerLookup annotation to the resource since HK2 uses singleton by default 3) Don't bind resource classes in guice modules, they'll be created by HK2 (however with the above code, you are expected to bind a ResourceConfig, which is where you register your resource classes for HK2) That's pretty much it. If you need additional jersey bindings to be available to guice at module creation time, just create new ones that delegate to the ServiceLocator again.
          Hide
          cowwoc added a comment -

          jhesse,

          Check if constructor injection works, especially if the constructor parameters contain some types that are injected by HK2 and others injected by Guice.

          Show
          cowwoc added a comment - jhesse, Check if constructor injection works, especially if the constructor parameters contain some types that are injected by HK2 and others injected by Guice.
          Hide
          jhesse added a comment -

          cowwoc,

          This code base uses only constructor injection. Pretty much all of the objects injected in resources are regular, pojos defined in guice modules. The HK2 -> guice delegation works fine. The guice -> HK2 (ServiceLocator) delegation seems to work fine too as a means to provide bindings to guice for Jersey based services.

          Show
          jhesse added a comment - cowwoc, This code base uses only constructor injection. Pretty much all of the objects injected in resources are regular, pojos defined in guice modules. The HK2 -> guice delegation works fine. The guice -> HK2 (ServiceLocator) delegation seems to work fine too as a means to provide bindings to guice for Jersey based services.
          Hide
          cplummer added a comment -

          @jhesse - how are you integrating this into the bootstrap? Are you using the GuiceFilter to drive it with your JerseyServletContainer in a ServletModule? Or is Jersey driving it from a ResourceConfig that you've set up? It'd be interesting to see (relevant portions of) your web.xml, ResourceConfig/Application class, and ResourceConfig binding if you wouldn't mind showing them.

          Show
          cplummer added a comment - @jhesse - how are you integrating this into the bootstrap? Are you using the GuiceFilter to drive it with your JerseyServletContainer in a ServletModule? Or is Jersey driving it from a ResourceConfig that you've set up? It'd be interesting to see (relevant portions of) your web.xml, ResourceConfig/Application class, and ResourceConfig binding if you wouldn't mind showing them.
          Hide
          jhesse added a comment -

          @cplummer: yeah, I'm using GuiceFilter. Here's an example server module:

          public class ExampleServerModule extends AbstractModule {
          
            private final Supplier<Injector> injectorSupplier;
          
            public ExampleServerModule(Supplier<Injector> injectorSupplier) {
              this.injectorSupplier = checkNotNull(injectorSupplier);
            }
          
            @Override
            protected void configure() {
              install(new JerseyModule(injectorSupplier));
              install(new ServletModule() {
                @Override
                protected void configureServlets() {
                  serve("/*").with(JerseyServletContainer.class);
                }
              });
            }
          
            @Provides
            @Singleton
            public ObjectMapper objectMapper() {
              return ObjectMapperContextResolver.mapper;
            }
          
            @Provides
            public ResourceConfig resourceConfig() {
              ResourceConfig rc = new ResourceConfig();
              rc.registerClasses(MyResource.class,
                  JacksonJsonProvider.class,
                  ObjectMapperContextResolver.class);
              return rc;
            }
          
            @javax.ws.rs.ext.Provider
            public static class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
              static final ObjectMapper mapper;
              static {
                mapper = new ObjectMapper();
                mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
                mapper.setSerializationInclusion(Include.NON_NULL);
              }
          
              @Override
              public ObjectMapper getContext(Class<?> arg0) {
                return mapper;
              }
            }
          }
          

          Then I configure it in a Jetty server like:

          ServletContextHandler app = new ServletContextHandler(options);
          app.setContextPath(contextPath);
          app.addFilter(GuiceFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST,
          	DispatcherType.FORWARD, DispatcherType.ASYNC, DispatcherType.INCLUDE,
          	DispatcherType.ERROR));
          app.addEventListener(new GuiceServletContextListener() {
          	@Override
          	protected Injector getInjector() {
          		return webInjector;
          	}
          });
          	
          ServletHolder ds = app.addServlet(DefaultServlet.class, "/");
          ds.setInitParameter("dirAllowed", "false");
          ,,,
          

          The webInjector is passed to the GuiceServletContextListener in my case. But, you should be able to create it from within a guice context listener implementation, as well.

          Show
          jhesse added a comment - @cplummer: yeah, I'm using GuiceFilter. Here's an example server module: public class ExampleServerModule extends AbstractModule { private final Supplier<Injector> injectorSupplier; public ExampleServerModule(Supplier<Injector> injectorSupplier) { this .injectorSupplier = checkNotNull(injectorSupplier); } @Override protected void configure() { install( new JerseyModule(injectorSupplier)); install( new ServletModule() { @Override protected void configureServlets() { serve( "/*" ).with(JerseyServletContainer.class); } }); } @Provides @Singleton public ObjectMapper objectMapper() { return ObjectMapperContextResolver.mapper; } @Provides public ResourceConfig resourceConfig() { ResourceConfig rc = new ResourceConfig(); rc.registerClasses(MyResource.class, JacksonJsonProvider.class, ObjectMapperContextResolver.class); return rc; } @javax.ws.rs.ext.Provider public static class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> { static final ObjectMapper mapper; static { mapper = new ObjectMapper(); mapper.configure(SerializationFeature.INDENT_OUTPUT, true ); mapper.setSerializationInclusion(Include.NON_NULL); } @Override public ObjectMapper getContext( Class <?> arg0) { return mapper; } } } Then I configure it in a Jetty server like: ServletContextHandler app = new ServletContextHandler(options); app.setContextPath(contextPath); app.addFilter(GuiceFilter.class, "/*" , EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC, DispatcherType.INCLUDE, DispatcherType.ERROR)); app.addEventListener( new GuiceServletContextListener() { @Override protected Injector getInjector() { return webInjector; } }); ServletHolder ds = app.addServlet(DefaultServlet.class, "/" ); ds.setInitParameter( "dirAllowed" , " false " ); ,,, The webInjector is passed to the GuiceServletContextListener in my case. But, you should be able to create it from within a guice context listener implementation, as well.

            People

            • Assignee:
              Unassigned
              Reporter:
              jwells
            • Votes:
              55 Vote for this issue
              Watchers:
              57 Start watching this issue

              Dates

              • Created:
                Updated: