Guice/HK2ブリッジを使う

2019年7月24日

ゴール

JerseyはHK2というDIフレームワークを使用しており、これは除去することはできない。一方で、使い慣れたGuiceを使いたい。なんとかJersey(JAX-RS)配下のオブジェクトにもGuice管理の依存性注入ができないものか。前提としては以下だ。

GuiceServletContextListenerを使う

Guiceにサーブレットをサービスさせるために、GuiceServletContextListenerは使わねばならない。ここでInjectorを作成しなければならない。つまり、以下だ。

web.xml

<listener>
  <listener-class>foo.bar.webServer.guice.FooBarServletContextListener</listener-class>
</listener>

FooBarServletContextListener.java

public class FooBarServletContextListener extends GuiceServletContextListener {
  private Injector injector;
  @Override
  public void contextInitialized(ServletContextEvent servletContextEvent) {
    injector = Guice.createInjector(modules);
    super.contextInitialized(servletContextEvent);       
  }
  @Override
  protected Injector getInjector() {
    return injector;
  }

JerseyのResourceConfigを使う

ResourceConfigを使ってJerseyをセットアップする。

web.xml

<servlet>
  <servlet-name>jaxrs-api</servlet-name>
  <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
  <init-param>
    <param-name>javax.ws.rs.Application</param-name>
    <param-value>foo.bar.webServer.apiSample.JerseyConfig</param-value>
  </init-param>
</servlet>
<servlet-mapping>
  <servlet-name>jaxrs-api</servlet-name>
  <url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>

JerseyConfig.java

public class JerseyConfig extends ResourceConfig {
  public JerseyConfig(ServiceLocator serviceLocator) {    
    // 同じパッケージ中のウェブリソースを見つけ出す
    packages(JerseyConfig.class.getPackageName());
  }

ServiceLocatorProviderが見つからない

検索してみるとServiceLocatorProviderを使う例が多いのだが、現時点で最新のJersey-2.29からはServiceLocatorProviderが削除されている。どのような理由か不明だが。最新のJerseyではServiceLocatorProviderを使う方式は使用できない。

また、これらの例では、ResourceConfigの中でGuiceのInjectorを作成してしまっており、GuiceServletContextListenerのことまでは考慮されていない。

こちらとしては、必ずGuiceServletContextListenerの中でInjectorを作成し、それをJersey側に使わせたいのである。呼び出しの順序としては、必ずこの順序になる。なぜなら、GuiceServletContextListenerはサーブレット初期化時に最初に呼び出されるからだ。

解決方法

以下のようにする。

FooBarServletContextListener.java

public class FooBarServletContextListener extends GuiceServletContextListener {
  public static Injector injector; // 外からアクセス可能なようにpublic staticにする
  @Override
  public void contextInitialized(ServletContextEvent servletContextEvent) {
    injector = Guice.createInjector(modules);
    super.contextInitialized(servletContextEvent);       
  }
  @Override
  protected Injector getInjector() {
    return injector;
  }

JerseyConfig.java

public class JerseyConfig extends ResourceConfig {  
  @Inject
  public JerseyConfig(ServiceLocator serviceLocator) {
    initGuiceIntoHK2Bridge(serviceLocator, FooBarServletContextListener.injector);    
    packages(JerseyConfig.class.getPackageName());
  }
  private void initGuiceIntoHK2Bridge(ServiceLocator serviceLocator, Injector injector) {
    GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
    GuiceIntoHK2Bridge guiceBridge = serviceLocator.getService(GuiceIntoHK2Bridge.class);
    guiceBridge.bridgeGuiceInjector(injector);
  }

注意事項

なお、Jersey管理のウェブリソースにインジェクトするには、com.google.inject.Injectではいけない。必ずjavax.inject.Injectでないと、HK2はGuiceから依存を引っ張ってこないようだ。

@Path("/employees")
public class Employees  {

  @javax.inject.Inject private Sample sample;

  @Path("/{id}")
  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public Employee getById(@PathParam("id") int id) {
    System.out.println("sample " + sample);    

参考