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で書くといった手段もあるようですが、全然知らないのと、あまり広くは受け入れられそうにない気がするので省略します