もともとGuiceを使っているプロジェクトがあってそれをSpringに移植したい,だとか,Guiceをバリバリ使っているコンポーネントをSpringのアプリに組み込むことで資源の再利用をしたい,だとか,そういう事になることがある.あるのです.この前実際になった.
で,"spring guice together" とかそういう適当な検索ワードで調べると大体「そんな事はするな」「そうしたい意味がわからん」「そう考える時点でおかしいのでは」などといった,「とにかくやるな」という結果が引っかかる.確かに.しかしやらなければならない時があるのだ……というわけでそのメモを記す.
TL;DR
やらないで済むならやらないほうが良い.
本編
ご存知の通りGuiceはGoogle製のDI Frameworkで,一方のSpring frameworkはWeb Application Frameworkである.この二者間になぜ共存の問題が発生するかというと,Spring Frameworkもその内部にDI Frameworkを持っているから,というかそもそもDI機構がSpringの中核を担っているからである.つまりDI Frameworkが2個ある状態となるなのでそれらを矛盾なく管理する必要が出てくる.どうすれば良いのか.
アプローチ
SpringのDIをメインで利用して,Guiceはサブとして扱うという方法を今回は採用した.簡単に言うと,SpringのDIコンテナに対してGuiceのInjectorをInjectするという手法.
こんな感じ.
@Configuration public class GuiceModuleConfig { @Bean Injector injector() { return Guice.createInjector(new AppModule()); } }
AppModule
はGuiceのModuleで,このModuleを元にinjectorを作成し,そのinjectorをSpringのDIにinjectしてSpring DIから取得可能なようにしておく.そんでもって以下のようにAppModule
で提供されているinjecteeをSpringのDIを通じて取得できるようにしてやる.
@Bean DataSource dataSource(final Injector injector) { return injector.getInstance(DataSource.class); } @Bean FluentLogger fluentLogger(final Injector injector) { return injector.getInstance(FluentLogger.class); }
つまりはSpringのDIにinjectしたGuiceのinjectorをSpring DI経由で引っ張ってきて,更にそのinjectorからGuice経由で任意のinstanceを取ってくるという感じ.とりあえずこれで基本的なGuiceとSpring FrameworkのDI機構を共存させることが可能となる.なおSpringのDIについてはGuiceがあろうとなかろうといつものように利用できる.
Guiceで言うところのServletScopes.REQUEST
使いたいときにどうするのか
@RequestScope
を使う.以下のように.
@Bean @RequestScope Connection connection(final Injector injector) { return injector.getInstance(Connection.class); }
しかしGuice経由でinstanceを取ってきている場合はこれだけでは不十分で,GuiceServletContextListener
を継承したClassをServletContextListener
として登録してやる必要がある.
@WebListener public class AppGuiceConfig extends GuiceServletContextListener { private ServletContext servletContext; @Override public void contextInitialized(final ServletContextEvent servletContextEvent) { servletContext = servletContextEvent.getServletContext(); super.contextInitialized(servletContextEvent); } @Override protected Injector getInjector() { return Guice.createInjector(new AppModule()); } }
なおSpring-Boot等でweb.xmlを利用しない場合は上記のように@WebListener
を用いてListenerとして認識させてやる必要がある.
雑感
2つのDI Frameworkを共存させ,管理する事となるので複雑さとコストが増すように感じる.また,適切に双方のinjecteeのライフサイクルのハンドリングも正しく行わなければならないのでその点においても複雑さが増大する.やらなくて済むのであればやらないに越したことは無いと思う.
以上です.我々はやっていっています.
[追記]
https://github.com/spring-projects/spring-guice
これ使えば楽に出来そうな雰囲気があるんだけど,maven centralに上がっていなくて面倒だったので採用を見送ったという経緯があります.未検証.