(その11) Springframeworkとの統合(1) Spring管理のBeanをGuiceに一括登録
GuiceはSpringと統合する為のクラスを用意しています。その名もずばりSpringIntegration。統合と言っても様々な形態が考えられると思いますが、現時点でGuiceが用意しているのは、Springのコンテナで管理されているBeanをGuiceから取得/injectできるようにするものです。それほど凝った事はしていなくて、シンプルなBeanFactoryのラッパーとなっています。
SpringIntegrationクラスにはpublicメソッドが2つしか無いので、両方見てみます。今回はbindAll()メソッド。
SpringIntegration#bindAllメソッドを呼び出すと、Springに名前で登録されているBeanが全てGuiceに登録されます。その際、Guiceコンテナに登録されるインデックス(Keyクラス)は「型と名前のセット」になっています。JavaDocには
For a Spring bean named "foo", this method creates a binding to the bean's type and @Named("foo").
とあります。私はこれでちょっとはまったので、それについても書いていきます。
なお、GuiceとSeasar2の統合手段として、SpringIntegrationクラスをS2用に移植したものを、id:shot6さんが公開されていますので、S2な方はそちらを試して頂ければと。恐らく動きは同じだと思います(違ったらごめんなさい)。
試してみる
まず、既存のSpring資産をそのまま統合するのが目的という事で、Springの定義をXMLで行います*1。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <bean id="bar" class="sample.guice.springintegration.Beans$BarImpl"> <constructor-arg index="0"> <ref local="tee" /> </constructor-arg> </bean> <bean id="tee" class="sample.guice.springintegration.Beans$TeeImpl"> <constructor-arg index="0"> <value>test</value> </constructor-arg> </bean> </beans>
クラスとインタフェースは想像がつくと思うので省略。で、Guiceに登録。
// XML定義ファイルから、SpringのBeanFacotryを生成 final ApplicationContext context = new ClassPathXmlApplicationContext("/sample/guice/springintegration/applicationContext.xml"); Injector injector = Guice.createInjector(new AbstractModule() { @Override protected void configure() { SpringIntegration.bindAll(binder(), context); } });
えらく簡単です。さっそくインスタンスを取得してみます。
Bar bar = injector.getInstance(Bar.class);
System.out.println(bar.getTee().getS());
実行結果。
Exception in thread "main" com.google.inject.ConfigurationException: Missing binding to sample.guice.springintegration.Beans$Bar.
ouch! Barで登録されていないと。そうでした、「型と名前」でbindされているので、型だけ(Bar.class)じゃ取得できないですね。名前をつけて宣言的にinjectするのははその10でやりましたが、プログラム的に取得する場合は下記のようになります。
// この指定は「@Names("bar")アノテーションがつけられたBar.class」というKeyを取得している。 Key<Bar> barKey = Key.get(Bar.class, Names.named("bar")); Bar bar = injector.getInstance(barKey); System.out.println(bar.getTee().getS());
実行結果。
Exception in thread "main" com.google.inject.ConfigurationException: Missing binding to sample.guice.springintegration.Beans$Bar annotated with @com.google.inject.name.Named(value=bar).
ouch! これはどこを間違っているんでしょうか…。
というところでちょっとはまったのですが、SpringIntegrationのソースを見たら判明。インデックスになっている「型と名前」の「型」は、beanFactory.getBean()で取得したインスタンスのClassそのものでした。つまり、Bar.class(インタフェース)ではなく、BarImpl.class(実装クラス)じゃないといかんのですね。
Key<BarImpl> barKey = Key.get(BarImpl.class, Names.named("bar")); Bar bar = injector.getInstance(barKey); System.out.println(bar.getTee().getS());
実行すると
Hero
しかし
…って、実装クラスを指定しなきゃいけなかったら、
@Inject void inject(@Named("bar") Bar bar) {
だとエラーで、
@Inject void inject(@Named("bar") BarImpl bar) {
にしないとダメなんじゃ?*3それ、なんか意味無くない?
何か間違っているのではと思って試してみましたが、やはりインタフェース指定では取得できません。まあ考えてみればSpringの定義ファイルには実装クラスしか書いていないから、インタフェースで登録されていないのは当然かもしれません。しかし使いたいのは実装クラスじゃなくてインタフェースな場合が大半です。
そこで、あるべき姿なのかどうかはともかく、bindAll()に下記のようなコードを追加してみました。
for(Class<?> ifc : type.getInterfaces()) {
bindBean(binder, beanFactory, name, ifc);
}
これで強引ながらもBar.classに対してBarImpl.classをinjectできました。
…しかしやってはみたものの、これだと使わないものを含めて全インタフェースを登録してしまいます。また、そもそもinjectする箇所ごとに@Named("foo")と書くのは、タイプセーフなGuiceのメリットを殺しているので好ましくありません。
という事で、一見非常にお手軽に見えたbindAll()はいまいち使い難いようです。そこで次回は、もう1つの統合手段であるfromSpring()を見てみます。
User's Guideの翻訳
ところで、id:iad_otomamayさんがGuiceのUser's Guideをえらい勢いで翻訳されています(もう殆ど終わりかけてます。すごい)。ご覧あれ。