Spring Java Configuration(1)

Guiceと Springframeworkの最大の違いはDIやAOPの設定をJavaコードで書くかXMLで書くかですが、実は(?)SpringでもJava コードによる設定の記述手段が用意されています*1。今回はその"Spring Java Configuration"を簡単に見てみます。なお、まだマイルストーン1なので、用意されているというよりは準備されている、もしくは計画中と言う方が適切かも知れず、今回試す内容も今後のリリースで色々変わる可能性があります。

Spring Java Configurationの最も基本的なサンプル

まずinjectされるサービス。インタフェースとその実装です。

public interface Service {
  String getResponse(String msg);
}

public class ServiceImpl implements Service {
  public String getResponse(String msg) {
    return "Re: " + msg;
  }
}

次に、inject場所であるClient。後でも書きますが、Guiceの場合と異なり、厳密な意味でのPOJOです。

public class SpringClient {
  private Service service;
 
  public SpringClient(Service service) {
    this.service = service;
  }
   
  public void execute() {
    System.out.println(service.getResponse("Hello"));
  }
  /** テスト用 **/
  Service getService() {
    return service;
  }
}

そしていよいよ設定するコードです。GuiceはModuleを実装したクラスでしたが、Spring Java Configurationではアノテーションで設定コードである事を示します。

import org.springframework.beans.factory.annotation.Bean;
import org.springframework.beans.factory.annotation.Configuration;
import org.springframework.beans.factory.annotation.Scope;

// 設定コードである事を示すアノテーション(色々オプションもありますが今回はデフォルトで使用)
@Configuration
public class SpringConfiguration {
  // XMLの<bean>定義に相当する事を示すアノテーション
  @Bean
  public Service service() {
    return new ServiceImpl();
  }
  // こちらではプロトタイプを指定(指定しないとシングルトンになるのは通常のSpring設定と同じ)
  @Bean(scope = Scope.PROTOTYPE)
  public SpringClient client() {
    return new SpringClient(service());
  }
}

@Beanをつけたメソッドがファクトリメソッドになるという事ですね。アノテーションが無ければ設定の為とは思えないような、普通のJavaコードになっています。
最後にコンテナを生成する起動部分。

public class SpringBootStrap {
  
  public static void main(String[] args) {
    // JavaConfiguration用のコンテナを生成
    AnnotationApplicationContext context
        = new AnnotationApplicationContext(SpringConfiguration.class);
    
    // コンテナからBeanを取得(このコードはXMLの場合と同じ)
    SpringClient client = (SpringClient)context.getBean("client");
    client.execute();
    
    SpringClient anotherClient = (SpringClient)context.getBean("client");
    // 自前のnewではなくちゃんとコンテナ管理されている事を確認する為、PrototypeとSingletonのインスタンスをチェック
    System.out.println("client == anotherClient : " + (client == anotherClient));
    System.out.println("client.getService() == anotherClient.getService() : "
        + (client.getService() == anotherClient.getService()));
  }
}

実行結果

Re: Hello
client == anotherClient : false
client.getService() == anotherClient.getService() : true

ちゃんとプロトタイプ指定したインスタンスは別に、シングルトン指定(デフォルト)のインスタンスは同じとなって実行されました。

感想

Guiceの手法と比べて際立つのは、Spring Java ConfigurationはアプリケーションのPOJOをフレームワークに依存させない(=汚染しない)ようにするという方針です。Guiceはinjectされる場所に@Injectアノテーションをつけるので、そのクラスやインタフェースがGuiceに依存してしまいます。しかしこのSpringの手法はXMLの場合同様、完全に外部からinject対象やinject場所を指定するので、アプリケーションのオブジェクトはクリーンなままです。
逆に言うとどこにinjectされるかが対象コードを見ただけだと分からないというデメリットもありますが、そこはIDEのプラグインがいずれ解決してくれそうなので、実際にはあまりデメリットにならない可能性が高いです。

ただ個人的な感想としては、設定コード(@Beanがついたメソッド群)が普通のJavaコードなのでコンテナやリフレクションの「臭い」がしない為、逆に気持ちが悪いというか、直感的に分からないというモヤモヤ感を感じてしまいました。普通にメソッド呼び出してnewしてる筈なのになんでシングルトンになるんだ、とかそういうところです。AOPもある意味同じなのですが、処理が追加されるのではなく、書いてある事と動作が(少し)違ってくるというのがどうも…。まあ慣れの問題も大きいと思うので、ちょっと経ったら「やっぱりこっちの方が良い」とか言い出すかも知れませんが。

という事で、今回は非常に単純な例を試してみました。XMLの設定とも共存できる(同時に使える)ので、必要に応じてどちらかを選んだり、上手く使い分けて両方使ったりが簡単にできそうな感じです。そいれでは次回はAOPを試してみたいと思います。

*1:Groovyで書くといった手段もあるようですが、全然知らないのと、あまり広くは受け入れられそうにない気がするので省略します