AspectJの第一歩 HelloWorld |
まず簡単な例として、HelloWorldプログラムにAspectJを適用してみましょう。
まず、ごく普通の単純なJavaプログラムであるHelloWorldクラスを用意します。
/** * HelloWorld.java * * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a> * @version */ package hello; public class HelloWorld { public static void main(String[] args) { new HelloWorld().sayHello(); } void sayHello() { System.out.println("Hello world!"); } } // HelloWorld |
パッケージhelloの下にHelloWorldクラスがあります。ここで、printMessage()メソッドが呼ばれる直前と呼ばれた直後にメッセージを出力するアスペクトを作成してみましょう。
アスペクトの記述方法には、以下の2つがあります。
AspectJ開発当初から使用されている形態です。Java言語に似たAspectJ言語でアスペクトを記述します。
/** * Trace.aj * * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a> * @version */ package hello; aspect Trace { pointcut atSayHello(): call(void HelloWorld.sayHello()); before(): atSayHello() { System.out.println("[Before sayHello]"); } after(): atSayHello() { System.out.println("[After sayHello]"); } }// Trace |
普通のJavaソースと似ていますが、これはAspectJのコードです。見慣れないキーワードとして、aspect、pointcut、call、before、afterが出ていますね。それぞれの詳しい説明は後ほど触れますが、下記のような機能を持っています。
この2つのファイルを次のようなディレクトリ階層に置きます。Javaと同じくパッケージに合わせています。
D:\ | +-- work +-- classes | +-- src +-- hello +-- HelloWorld.java +-- Trace.aj |
Java 2 Standard Edition 5.0から導入されたアノテーションを使ってアスペクトを記述します。
/** * Trace.java * * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a> * @version */ package hello; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.After; @Aspect class Trace { @Before("call (void HelloWorld.sayHello())") public void beforeAtSayHello() { System.out.println("[Before sayHello]"); } @After("call (void HelloWorld.sayHello())") public void afterAtSayHello() { System.out.println("[After sayHello]"); } }// Trace |
普通のJavaと同じ文法で記述していますが、アノテーションによってAspectJのアスペクトを記述しています。アノテーションの中に、AspectJ言語におけるポイントカット定義を記述しています。
アノテーション記述とはいえ、AspectJ言語の記述を知らなくてはアスペクトの記述はできません。
アスペクトのコンパイルには、アスペクトをウィービングする対象のソースファイルと一緒にコンパイルする方法と、アスペクトだけコンパイルしておいて、実行時にウィービングする方法があります。
AspectJが提供するコマンドを使用する場合は、環境変数CLASSPATHにAspectJのライブラリファイルを設定する必要があります。
D:\work> set CLASSPATH=C:\java\aspectj1.5\lib\aspectrt.jar D:\work> |
アスペクトをコンパイルするときは、ajc(AspectJコンパイラ)コマンドを使用します。ajcコマンドを使うときは、アスペクトを記述したソースファイル(ここではTrace.aj)と、アスペクトを織りこむ対象となるソースファイル(ここではHelloWorld.java)の両方を一緒に指定します。
D:\work> dir /B classes src D:\work> dir /B /S src D:\work\src\hello D:\work\src\hello\HelloWorld.java D:\work\src\hello\Trace.aj D:\work> ajc -d classes src\hello\HelloWorld src\hello\Trace.aj work$ ls classes/hello D:\work> dir /B /S classes D:\work\classes\hello D:\work\classes\hello\HelloWorld.class D:\work\classes\hello\Trace.class D:\work> |
ajcコンパイラを使ってHelloWorldクラスとTraceアスペクトのコンパイルが成功しました。
では、早速HelloWorldを実行してみましょう。
D:\work> java -cp %CLASSPATH%;classes hello.HelloWorld [Before sayHello] Hello world! [After sayHello] D:\work> |
もし、ajcコマンドを使っていても、アスペクトを一緒に指定しないと、アスペクトの織りこみが行われません。
D:\work> ajc -d classes src\hello\HelloWorld.java D:\work> java -cp %CLASSPATH%;classes hello.HelloWorld Hello world! D:\work> |
意外とアスペクトを一緒に指定するのを忘れたりします。おかしいおかしいとソースコードを何度も見直してみて、間違いはないはずなのに、と悩んでしまいますのでご用心。
Javaアノテーションの場合もコンパイルするときはajc(AspectJコンパイラ)コマンドを使用します。ajcコマンドを使うときは、アスペクトを記述したソースファイル(ここではTrace.java)と、アスペクトを織りこむ対象となるソースファイル(ここではHelloWorld.java)の両方を一緒に指定します。
D:\work> dir /B /S src D:\work\src\hello D:\work\src\hello\HelloWorld.java D:\work\src\hello\Trace.java D:\work> ajc -1.5 -d classes src\hello\*.java D:\work> dir /B /S classes D:\work\classes\hello D:\work\classes\hello\HelloWorld.class D:\work\classes\hello\Trace.class D:\work> |
-1.5を指定しないと、アノテーションに関するエラーが発生しました。(なぜか疑問ですが、とりあえず指定して回避)
ajcコンパイラを使ってHelloWorldクラスとTraceアスペクトのコンパイルが成功しました。では、早速HelloWorldを実行してみましょう。
D:\work> java -cp %CLASSPATH%;classes hello.HelloWorld [Before sayHello] Hello world! [After sayHello] D:\work> |
AspectJ言語でもJavaアノテーションで記述したアスペクトでも構わないので、アスペクトだけを先にコンパイルします。そのアスペクトに関する情報をAspectJ専用のマニフェスト・ファイルに記述してJAR形式のファイルを作成します。
J2SE 5.0から導入されたbytecode instrumentation機能を使ってプログラムを実行時にアプリケーションのクラスがJavaVMにロードされるタイミングでウィービングを行います。
D:\ +-- work +-- classes | +-- HelloWorld.class | +-- Trace.class | +-- META-INF | +-- aop.xml | +-- src +-- hello +-- HelloWorld.java +-- Trace.aj |
META-INF/aop.xmlというAspectJ用のマニフェストファイルをJARファイル中に含めます。
<?xml version="1.0"?> <aspectj> <aspects> <aspect name="Trace"/> </aspects> <weaver options="-verbose"> <include within="*"/> </weaver> </aspectj> |
先にコンパイルしたTrace.classと上記のマニフェストファイルaop.xmlを含めたJARファイルを作成します。
D:\work> jar cvf trace.jar META-INF -C classes hello\Trace.class マニフェストが追加されました。 エントリ META-INF/ を無視します。 META-INF/aop.xml を追加中です。(入 = 164) (出 = 112)(31% 収縮されました) \hello/Trace.class を追加中です。(入 = 3009) (出 = 1108)(63% 収縮されました) D:\work> |
AspectJのコマンドaj5.batを使用すると、簡単にロードタイム・ウィービングの実行ができます。
D:\work> aj5 -cp trace.jar;classes hello.HelloWorld [Before sayHello] Hello world! [After sayHello] D:\work> |
aj5.batではなく、javaでオプションを指定して実行してみます。
D:\work> aj5 -javaagent:C:\java\aspectj1.5\lib\aspectjweaver.jar -cp trace.jar;classes hello.HelloWorld [Before sayHello] Hello world! [After sayHello] D:\work> |
アスペクトは、クラスとは別な観点のモジュールです。AspectJでは、キーワードclassに代わってaspectとして定義します。classと同様パッケージ指定をすることができます。
package hello; aspect Trace { : } |
アスペクトには、3つの概念<join point>、<advise>、および<introduction>があります。<join point>は、あらかじめ定義されているプログラムの流れの上の点を示します。アスペクトで定義する機能を織りこむ地点を指定するために使用されます。<advise>は、織りこむ機能(実行させたいコード)のことです。<introduction>は、クラスにフィールドやメソッドを追加したり、継承関係を変更します。
AspectJでは、<join point>として次のような指定ができます。
<join point>を選択するのには、pointcut構文を使用します。
書式 pointcut ユーザ定義名(パラメータリスト) : <join point>の指定‥‥‥ ;
HelloWorldサンプルでは、<join point>の1つであるcall(メソッドの呼び出し)を選択するpointcutを定義し、その名前としてatSayHelloを付けています。callの具体的な指定として、HelloWorldクラスのsayHelloメソッド(引数なしで戻り型はvoid)を指定しています。
pointcut atSayHello(): call(void HelloWorld.sayHello()); |
pointcutで指定できる<join point>には以下の種類があります。<join point>を複数組み合わせて複雑なpointcutを定義することもできます。
pointcutで指定可能な<join point> | 意味 | |
---|---|---|
call | メソッドの呼び出し | |
execution | メソッドの実行 | |
get | フィールドの読み出し | |
set | フィールドへ書き込み | |
initialization | インスタンス生成 | |
staticinitialization | クラス初期化 | |
handler | 例外ハンドラの実行(catch) | |
this | 指定した<join point>が実行されているインスタンスを指す | |
target | <join point>が対象としているインスタンスを指す | |
args | <join point>において変数を取り込む場合に指定 | |
cflow | <join point>がどの制御フローにおいて実行されたかを指定する | |
cflowbelow | ||
within | <join point>が有効となるパッケージ・クラスを指定する | |
withincode | <join point>が有効となるメソッドを指定する | |
if |
pointcutで指定可能な演算子には、&&、||、! があります。
<join point>において織りこむコードを<advice>と呼びます。<advice>には、次の種類があります。
<advice> | 意味 | |
---|---|---|
before | <join point>が処理される前に織りこむ | |
after | <join point>が処理された後に織りこむ | |
after() returning | <join point>が処理され正常に復帰した後に織りこむ | |
after() throwing | <join point>が処理され例外が発生した後に織りこむ | |
around | <join point>に置き換えて織りこむ | |
declare warning | <join point>をコンパイル時に警告として扱う | |
declare error | <join point>をコンパイル時にエラーとして扱う |
書式 before(パラメータ) : pointcut名 { 織りこむコード } after(パラメータ) : pointcut名 { 織りこむコード } after(パラメータ) returning : pointcut名 { 織りこむコード } after(パラメータ) throwing : pointcut名 { 織りこむコード } around(パラメータ) : pointcut名 { 織りこむコード } declare warning : pointcut名 : 警告メッセージ declare error : pointcut名 : エラーメッセージ
HelloWorldサンプルでは、<advice>としてbeforeおよびafterを使用しています。
before(): atSayHello() { System.out.println("[Before sayHello]"); } after(): atSayHello() { System.out.println("[After sayHello]"); } |