(その13) Springframeworkとの統合(2) Spring管理のBeanをGuiceに個別登録

Springとの統合手段2つ目、SpringIntegration#fromSpringメソッドについてです。
fromSpring()メソッドのインタフェースは以下のようになっています。

/** Creates a provider which looks up objects from Spring using the given name. */
public static <T> Provider<T> fromSpring(Class<T> type, String name)

戻りの型Providerは前回見た通り、injectするインスタンスを提供するインタフェースです。JavaDocコメントによれば、Springから指定した名前でオブジェクトを取得してくれるそうです。

fromSpring()を使ってみる。

XML定義とBean関連はその11と同じものを使用しますので省略。
bind部分とGuiceコンテナから取得する部分を一気に。

    // Springの定義ファイル読み込み
    final ApplicationContext context = 
        new ClassPathXmlApplicationContext("/sample/guice/springintegration/applicationContext.xml");
    Injector injector = Guice.createInjector(new AbstractModule() {
      protected void configure() {
        // fromSpring()はBeanFactoryのbindが必要
        bind(BeanFactory.class).toInstance(context);
        bind(Bar.class).toProvider(fromSpring(Bar.class, "bar"));
      }
    });
    Bar bar = injector.getInstance(Bar.class);
    System.out.println(bar.getTee().getS());

実行結果は

Hero

今回はめでたく一発で取得できました。1つ目の統合方法であるbindAll()と違って、ちゃんとインタフェースで扱う事が可能になっています。

APIを理解する

改めてfromSpring()を使う部分を見てみます。

bind(Bar.class).toProvider(fromSpring(Bar.class, "bar"));
  • bindでBarインタフェースをinject先として指定し、
  • toProviderで、インスタンスをProviderから受け取ると指定し、
  • fromSpringでProviderを作成

しています。なお、ここだけ見るとfromSpring()の第一引数Bar.classの用途が分かりませんが、APIを見ていくと以下のようになっている事が分かります。

public abstract class AbstractModule implements Module {
  protected <T> AnnotatedBindingBuilder<T> bind(Class<T> clazz) {}
}
// ↓
public interface AnnotatedBindingBuilder<T> extends LinkedBindingBuilder<T> {
  ScopedBindingBuilder toProvider(Provider<? extends T> provider);
}
// ↓
public class SpringIntegration {
  public static <T> Provider<T> fromSpring(Class<T> type, String name) {}
}

つまりbindする型とProviderで取得する型を一致させる為総称型が使われているので、fromSpring()も総称メソッドになっているんですね。まあBeanFactoryがObject getBean(String)なので結局XMLで定義したクラスが誤っていれば実行時エラーになる訳ですが、それはGuice側では解決できないです(だから全部Javaで書くのが良いんだ、という話になりますね)。
ちなみに、以下のようなヘルパメソッドを作ればちょびっとだけ記述を減らせます。

  private <T> void bindFromSpring(Class<T> type, String name) {
    bind(type).toProvider(fromSpring(type, name));
  }

という訳で、fromSpring()メソッドを使えば、インタフェースを指定してSpring定義のBeanをinjectできる事が分かりました。bindAll()と比べると記述量は増えるものの、SpringのBeanを適切な型でそのまま使えるのは魅力です。

SpringIntegrationのソースをちょっと覗いてみる

SpringからどうやってBeanを取得しているのか気になるので、ソースを見てみました。

  // fromSpring()でこのProviderのインスタンスが作られる。
  static class InjectableSpringProvider<T> extends SpringProvider<T> {
    InjectableSpringProvider(Class<T> type, String name) {
      super(type, name);
    }

    // BeanFactoryがinjectされる。
    @Inject
    @Override
    void initialize(BeanFactory beanFactory) {
      super.initialize(beanFactory);
    }
  }
  //...
    void initialize(BeanFactory beanFactory) {
      this.beanFactory = beanFactory;
      // 指定された名前のBeanが指定された型に一致するかどうかチェック
      if (!beanFactory.isTypeMatch(name, type)) {
  //...
    public T get() {
      // injectする際に呼び出される。シングルトンならキャッシュしているインスタンスを、
      // そうでなければBeanFactoryから取得したインスタンスを返す。
      return singleton ? getSingleton() : type.cast(beanFactory.getBean(name));
    }

つまりこのSpringIntegrationで行っているのはBeanFacotryのラッパー(Adapter?)ですね。従って、生成されるインスタンスやパフォーマンスや動作はSpringそのままである事に留意する必要があります。

追記

上記「APIを理解する」でちょっと書いた総称型の話が、iad_otomamayさんのところで丁寧に説明されていました。勉強になります。やはり、実際に使うコードを例に採ると分かり易いですね。