JDK 5.0では「アノテーション(JSR-175)」と呼ばれる新しい言語仕様が追加された。アノテーションはXDocletを使ったことがある人ならすぐに理解できると思われるが、接頭辞@を持つタグをソースコード中に記述して、さまざまな付加的要素をコードに加えることができる(XDocletではタグによってコードの自動生成を指示する)。
JBoss AOPではこのアノテーション機能をサポートしており、AOPの目的の1つでもある「カプセル化した振る舞いを透過的に提供する」という部分をアノテーションによって補完している。
では、このアノテーションを用いたAOPの例を見ていこう。先ほどのMessengerクラスを次のように書き換えてほしい。
public class Messenger { @Trace public Messenger() {} @Trace @Greet public void printMessage(String name) { System.out.println(name); } }
ここで記述されている「@Trace」や「@Greet」がJDK 5.0で導入されたアノテーションのタグである。対応する2つのインターフェイスを作成しておく。
public @interface Trace {}
public @interface Greet {}
@Greetタグに対応するインターセプタは先ほどのGreetingInterceptorを使うとして、@Traceタグに対応するインターセプタ「TraceInterceptor」を新たに作成する。このインターセプタはメソッドやコンストラクタ呼び出し、フィールド値の読み書きなどをトレース(追跡)するためのインターセプタである。
import org.jboss.aop.joinpoint.*; import org.jboss.aop.advice.Interceptor; public class TraceInterceptor implements Interceptor { public String getName() { return "TraceInterceptor"; } public Object invoke(Invocation invocation) throws Throwable { String msg = ""; if (invocation instanceof MethodInvocation) { msg = ((MethodInvocation)invocation).getMethod().toString(); } else if (invocation instanceof ConstructorInvocation) { msg = ((ConstructorInvocation)invocation).getConstructor().toString(); } else if (invocation instanceof FieldReadInvocation) { msg = ((FieldReadInvocation)invocation).getField().toString(); } else if (invocation instanceof FieldWriteInvocation) { msg = ((FieldWriteInvocation)invocation).getField().toString(); } else { msg = invocation.getClass().getName().toString(); } try { System.out.println("TRACE START : " + msg); return invocation.invokeNext(); } finally { System.out.println("TRACE END"); } } }
さらにMessengerクラスのprintMessage()メソッドを非staticにしたため、Testerクラスもインスタンスを生成するように変更しておこう。最後にjboss-aop.xmlの記述を次のように変更する。
<?xml version="1.0" encoding="UTF-8"?> <aop> <bind pointcut="all(@trace)"> <interceptor class="TraceInterceptor" /> </bind> <bind pointcut="execution(* Messenger->@greet(..))"> <interceptor class="GreetingInterceptor" /> </bind> </aop>
この記述では@Greetタグと@Traceタグをジョインポイントとして定義し、それぞれのインターセプタを指定している。
run: [java] TRACE START : public Messenger() [java] --- Constructor --- [java] TRACE END [java] TRACE START : public static void Messenger.printMessage(java.lang.String) [java] Hello, JBoss AOP [java] TRACE END
コンストラクタに対してはTraceInterceptorのみが適用され、printMessage()メソッドに対してはTraceInterceptorとGreetingInterceptorの両方が適用されていることが分かる。
このようにアノテーションを利用することで、開発者が各ジョインポイントに対して適用するインターセプタの制御が簡単で分かりやすいものとなることが理解いただけただろうか。
JBoss AOPは本来J2EEサーバであるJBoss ASに適用するために開発されたものである。そのためここではEJBの次期バージョンである「EJB 3.0」のコーディングを取り上げ、どのようにアノテーションによるAOPが実現されているのか見てみよう。
まずは次に示すEJB 3.0による簡単なStateless Session Beanのサンプルコードを見ていただきたい。
import javax.ejb.Remote; @Remote public interface HelloRemote { public String sayHello(String name); }
import javax.ejb.Stateless; @Stateless public class HelloBean implements HelloRemote { public String sayHello(String name) { return "Hello, " + name; } }
HelloRemoteはリモートインターフェイスであり、@Remoteタグによってそれを指定している。このHelloRemoteをimplementsするHelloBeanがEnterprise Bean本体であり、@StatelessタグはこのBeanが「Stateless Session Bean」であることを印付け、EJBコンテナに対してそのように振る舞うことを指定している。
サーバ側の開発はたったこれだけである。従来のようにEJBObjectを継承したComponentインターフェイスや、EJBHomeを継承するHomeインターフェイスを作成する必要もなければ、煩雑なDDの記述に悩まされることもない。また、Bean本体にもコールバックメソッドの実装を記述する必要もない。
サーバ側だけでなく、呼び出すクライアント側もコーディングが簡単になっている。従来のようにHomeインターフェイスの参照やナローキャスティングすることなく、単にルックアップするだけでBeanを使用することができる。
public class Client { public static void main(String[] args) throws Exception { InitialContext ctx = new InitialContext(); HelloRemote hello = (HelloRemote)ctx.lookup(HelloRemote.class.getName()); System.out.println(hello.sayHello("EJB3")); } }
EJB 3.0で取り入れられたこのような手法は「アスペクト指向アノテーション」とも呼ばれており、EJBのサービスレベルのアスペクトを@タグを用いてコードに適用することで、EJB開発をより簡単で透過的なものにしている。
JBoss AOPではコンパイル時の静的なインターセプションだけでなく、実行時に動的にアドバイスを追加したり取り除いたりすることが可能なDynamic AOPという仕組みが提供されている。Dynamic AOPを用いることで次のような2つの機能を実現できる。
それでは、簡単なDynamic AOPの例を見てみよう。Dynamic AOPを使用するためにはjboss-aop.xmlに次のように
<?xml version="1.0" encoding="UTF-8"?> <aop> <prepare expr="all(Messenger)"/> </aop>
次にTesterクラスを以下のように書き換えて実行する。
import org.jboss.aop.*; import org.jboss.aop.advice.*; public class Tester { public static void main(String[] args) throws Exception { Messenger msg = new Messenger(); msg.printMessage("JBoss AOP"); AdviceBinding binding = new AdviceBinding("execution(* Messenger->printMessage(..))", null); binding.addInterceptor(GreetingInterceptor.class); AspectManager.instance().addBinding(binding); msg.printMessage("JBoss AOP"); } }
run: [java] JBoss AOP [java] Hello, JBoss AOP
最初のprintMessage()メソッドの呼び出し時(8行目)にはインターセプションは実行されない。ソースコードの10〜13行目の部分がホットデプロイメントの記述であり、2回目のprintMessage()メソッドの呼び出し(15行目)時にはインターセプションが実行されていることが分かる。
次にインスタンスに対してインターセプタを適用する例を見てみよう。
import org.jboss.aop.*; import org.jboss.aop.advice.*; public class Tester { public static void main(String[] args) throws Exception { Messenger msg1 = new Messenger(); Messenger msg2 = new Messenger(); Advised advised = (Advised)msg2; advised._getInstanceAdvisor().insertInterceptor( new GreetingInterceptor()); msg1.printMessage("JBoss AOP No.1"); msg2.printMessage("JBoss AOP No.2"); } }
run: [java] JBoss AOP No.1 [java] Hello, JBoss AOP No.2
この例ではMessengerクラスのインスタンスmsg1とmsg2を生成し、msg2に対してのみインターセプタを適用するようにコーディングしている(10〜12行目)。これにより、実行結果からも分かるとおりインスタンスmsg2にだけ「Hello, 」の文字列が追加されることになる。
以上ざっとJBoss AOPの実装がどのようなものかを見てきたが、いかがだっただろうか。同じAOPの実装でもAspectJとJBoss AOPの違いを確かめることもできたと思う。しかし、実装が異なるとはいえ、どちらもアスペクト指向プログラミングのためのフレームワークであるということに変わりはない。「アスペクト」という“ソフトウェアの持つさまざまな「側面」”を、コアロジックから分離するための仕組み。それを開発者に提供することこそが、AOPフレームワークの役目なのである。
次回は米BEA SystemsもスポンサーとしてバックアップするAspectWerkzを取り上げたい。
Copyright © ITmedia, Inc. All Rights Reserved.