Guice configuration error

問題点

Guiceにて以下のようなエラーが発生した。

foo.bar.dispatch.rpc.shared.ServiceException: Guice configuration errors:

1) Unable to create binding for javax.servlet.http.HttpServletRequest. It was already configured on one or more child injectors or private modules
    bound at com.google.inject.servlet.InternalServletModule.provideHttpServletRequest() (via modules: barfoo.web.server.core.DispatchServletModule -> com.google.inject.servlet.InternalServletModule)
  If it was in a PrivateModule, did you forget to expose the binding?
  while locating javax.servlet.http.HttpServletRequest
    for field at com.gwtcenter.gwtprpc.server.RequestInfo.request(RequestInfo.java:13)
  while locating com.google.inject.Provider<com.gwtcenter.gwtprpc.server.RequestInfo>
    for field at com.gwtcenter.gwtprpc.server.RequestInfoFactory$Default.provider(RequestInfoFactory.java:11)
  at com.gwtcenter.gwtprpc.server.RequestInfoFactory.class(RequestInfoFactory.java:1)
  while locating com.gwtcenter.gwtprpc.server.RequestInfoFactory
    for field at com.gwtcenter.gwtprpc.server.AbstractRpcHandler.acProvider(AbstractRpcHandler.java:21)
  while locating barfoo.web.server.handler.RpcHandler

2) Unable to create binding for javax.servlet.ServletContext. It was already configured on one or more child injectors or private modules
    bound at com.google.inject.servlet.InternalServletModule.configure(InternalServletModule.java:98) (via modules: barfoo.web.server.core.DispatchServletModule -> com.google.inject.servlet.InternalServletModule)
  If it was in a PrivateModule, did you forget to expose the binding?
  while locating javax.servlet.ServletContext
    for field at com.gwtcenter.gwtprpc.server.RequestInfo.servletContext(RequestInfo.java:13)
  while locating com.google.inject.Provider<com.gwtcenter.gwtprpc.server.RequestInfo>
    for field at com.gwtcenter.gwtprpc.server.RequestInfoFactory$Default.provider(RequestInfoFactory.java:11)
  at com.gwtcenter.gwtprpc.server.RequestInfoFactory.class(RequestInfoFactory.java:1)
  while locating com.gwtcenter.gwtprpc.server.RequestInfoFactory
    for field at com.gwtcenter.gwtprpc.server.AbstractRpcHandler.acProvider(AbstractRpcHandler.java:21)
  while locating barfoo.web.server.handler.RpcHandler

2 errors
    at foo.bar.dispatch.rpc.server.AbstractDispatchServiceImpl.execute(AbstractDispatchServiceImpl.java:111)
    at barfoo.web.server.core.MyDispatchServiceImpl.execute(MyDispatchServiceImpl.java:67)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:587)
    at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:333)
    at com.google.gwt.user.server.rpc.RemoteServiceServlet.processCall(RemoteServiceServlet.java:303)
    at com.google.gwt.user.server.rpc.RemoteServiceServlet.processPost(RemoteServiceServlet.java:373)
    at com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet.doPost(AbstractRemoteServiceServlet.java:62)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)
    at barfoo.web.server.core.MyDispatchServiceImpl.service(MyDispatchServiceImpl.java:49)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at com.google.inject.servlet.ServletDefinition.doServiceImpl(ServletDefinition.java:290)
    at com.google.inject.servlet.ServletDefinition.doService(ServletDefinition.java:280)
    at com.google.inject.servlet.ServletDefinition.service(ServletDefinition.java:184)
    at com.google.inject.servlet.ManagedServletPipeline.service(ManagedServletPipeline.java:89)
    at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:85)
    at com.google.inject.servlet.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:120)
    at com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:133)
    at barfoo.web.server.core.MyGuiceFilter.doFilter(MyGuiceFilter.java:21)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
    at org.eclipse.jetty.server.Server.handle(Server.java:499)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
    at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:544)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
    at java.lang.Thread.run(Thread.java:745)

簡単な再現コード

Misleading error message with child injectorという書き込みを発見する。これを簡単に再現するものとしては以下だ。

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;

public class Sample {

  public static void main(String[] args) {
    Injector parent = Guice.createInjector();
    Injector child = parent.createChildInjector(new AbstractModule() {
      @Override
      protected void configure() {
        bind(IStuff.class).to(Stuff.class);
      }
    });
    parent.getInstance(IStuff.class);
  }

  static interface IStuff {
  }

  static class Stuff implements IStuff {
  }
}

つまり、子インジェクタでバインドしたものについて親インジェクタを使ってインスタンスを作成することはできないらしい。このコードの実行結果は以下になる。

Exception in thread "main" com.google.inject.ConfigurationException: Guice configuration errors:

1) Unable to create binding for Sample$IStuff. It was already configured on one or more child injectors or private modules
    bound at Sample$1.configure(Sample.java:14)
  If it was in a PrivateModule, did you forget to expose the binding?
  while locating Sample$IStuff

1 error
    at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1075)
    at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1034)
    at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1086)
    at Sample.main(Sample.java:17)

ちなみに、先の書き込みは単純に「エラーの文言が不適当だ」という議論でしかない。

予想される原因

もともとのエラーを起こしたプログラムの構成は以下のようになっている。

  • 本体プログラム:ここでは組み込みのJettyサーバとGuiceを使用している。
  • warプログラム:上記にデプロイして動作させる。この中でもGuiceを使用している。

ここでwarプログラムの中で、本体側のGuiceインジェクタの子インジェクタを作成し、いくつかのバインドを行っている。

Jettyを動作させているのは、あくまでも本体側であり、親インジェクタを使用しているのだが、それがwarを呼び出すと、その中では子インジェクタによるバインディングが必要になっている。これがいけないらしい。

解決策

最も簡単な解決策としては、「子インジェクタを作成しない」ということだ。

本体プログラムに組み込まれるJettyがwarプログラムを呼び出すのだから、事前にwarプログラムの中のGuiceモジュールを処理してしまう、つまりモジュール登録してしまうことが考えられる。

war側では既にモジュール登録されているので、子インジェクタを作成する必要はない。単純に本体プログラムと同じインジェクタを使用すればよい。