Spring Java Configuration(2)

1回目に続き、Spring Java Configurationを試してみたいと思います。今回はAOP。ちなみに付属のドキュメントにはAOPの説明が無かったので(さすがM1リリース)、テストケースを参考に試してみます。

Spring Java ConfigurationでのAOP定義

injectするServiceのインタフェースと実装クラス、そしてinjectされるClient実装クラスは、前回と同じものを使用します。AOPは既存の実装を変更せずに透過的に処理を追加(変更)できるのが売りですから、当然と言えば当然です。
で、前回のコンフィギュレーションクラスにAOPの定義を追加して、インターセプタも書いちゃいます。

@Configuration
public class SpringConfigurationWithAOP {
  
  @Bean
  public Service service() {
    return new ServiceImpl();
  }
  
  @Bean(scope = Scope.PROTOTYPE)
  public SpringClient client() {
    return new SpringClient(service());
  }
  // 今回の追加部分。@SpringAdviceによってweavingする場所(Pointcut)を指定
  @Bean
  @SpringAdvice("execution(* sample.guice.service.*.*(..))")
  public LoggingInterceptor interceptor() {
    return new LoggingInterceptor();
  }
  
  // weavingするインターセプタ
  static class LoggingInterceptor implements MethodInterceptor {
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
      String methodName = methodInvocation.getMethod().getName();
      
      System.out.println("before: " + methodName);
      Object obj = methodInvocation.proceed();  // 本来の処理を実行
      System.out.println("after: " + methodName);
      
      return obj;
    }
  }
}

なおPointcutの指定は、Spring2.0のドキュメントを参考にしました。
では実行してみましょう。

Exception in thread "main" org.springframework.beans.factory.BeanDefinitionStoreException: sample.guice.springjavaconf.SpringConfigurationWithAOP$LoggingInterceptor contains no Bean creation methods or Aspect methods
at org.springframework.beans.factory.java.ConfigurationProcessor.process(ConfigurationProcessor.java:179)
(以下略)

ぎゃふん。インターセプタにBeanの定義もAspectの定義も無いと。そりゃ確かに無いけど、だから@Configurationつけてないんですが…。もしかしてアノテーションが内部クラスに引き継がる仕様なんでしょうか?よく分かりませんが、M1であまり深追いしても仕方ないのでやめときます。
とりあえずLoggingInterceptorを独立したクラスとして外に出して、再度実行。

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

できた! beforeとafterがちゃんと出力されているので、正しく実行されたようです。

アノテーションに対してAOPをかけてみる

Guiceのトランザクションの回で、@Transactionalアノテーションをつけたメソッドに対して下記のようにインターセプタを指定しました。

bindInterceptor(any(), annotatedWith(Transactional.class), transactionInterceptor);

Spring Java Configでもできるはずという事で、アノテーションをつけたメソッドをインターセプトする指定を試してみます。
まずServiceImplにアノテーションをつけます。

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

今回は単にログを出すだけなので@Transactionalは不適切かもしれませんが、分かり易いのでドンマイ。
そしてAOPの指定ですが、さっきのここを見るとPointcutの例として

@annotation(org.springframework.transaction.annotation.Transactional)

ってのがあるので、これをそのまま@SpringAdviceに書いてみます。

  @Bean
  @SpringAdvice("@annotation(org.springframework.transaction.annotation.Transactional)")
  public LoggingInterceptor interceptor() {
    return new LoggingInterceptor();
  }

そして実行

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

おぉっ、一発で上手くいきました。

考察

えらいあっさりしてますが、Spring Java ConfigurationではAOPの定義も簡単にできる事が分かりました。AspectJなPointcut文法がタイプセーフではない(SpringIDE使うと解決したりするのかも?たしか昔AspectJ用のEclipseプラグインで、Joinpointをマーキングしたりできたし)のはディスアドバンテージですが、その分指定方法は多彩で強力ですね。
という事で、2回に渡りSpring Java Configurationを簡単に見てみました。設定を全て外部化するという意味ではGuiceよりもクリーンなソリューションだと思います。一方でSpring in Actionの人のブログ(このエントリは私のよりずっとちゃんと考察しているので、GuiceとSpring Java Configurationの比較に興味がある方はコメント欄含めて必見です)では、コメント欄でGuiceのBob Lee氏が

In the Real World, you use dependencies much more often (M) than you define them (N). If Spring were an O(N*M) solution, Guice would be an O(N) solution.

なんて反論を寄せたりもしています。これは普通injectするServiceよりもinjectされるクラス(StrutsであればActionなど)の方が多いけど、Spring Java Configurationの場合こういうケースだと@BeanのメソッドをAction数分書かなきゃいけないんじゃないの?Guiceなら@Injectって書くだけだよ?って話です。確かにinject箇所の明示が必須条件だとすると、そういう事になりそうです。その辺が今後のリリースで解決されるのかされないのかも引き続き注目していきたいところです。

Spring Java Configurationの今後

このプロジェクトのリーダはCostin Leau氏です。氏はプロジェクトの発端となったRod Johnson氏のブログのコメントで

Spring JavaConfig is anything but dead. I plan to spend more time on it in April and have a release out ASAP - probably end of April/May.

と発言しているので、あと1ヵ月くらい待つと進捗するかも知れません。楽しみです。