[AspectJ Indexページへ戻る]

To AspectJ index page

AspectJの第一歩 HelloWorld

 


簡単なアスペクトの使用例

まず簡単な例として、HelloWorldプログラムにAspectJを適用してみましょう。

アスペクトの記述

まず、ごく普通の単純なJavaプログラムであるHelloWorldクラスを用意します。

普通の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つがあります。

  1. AspectJ言語による記述
  2. Javaアノテーションによる記述

AspectJ言語による記述

AspectJ開発当初から使用されている形態です。Java言語に似たAspectJ言語でアスペクトを記述します。

AspectJ言語によるTraceアスペクト記述
/**
 * 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アノテーションによる記述

Java 2 Standard Edition 5.0から導入されたアノテーションを使ってアスペクトを記述します。

アノテーションによるTraceアスペクト記述
/**
 * 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言語の記述を知らなくてはアスペクトの記述はできません。

アスペクトのコンパイル

アスペクトのコンパイルには、アスペクトをウィービングする対象のソースファイルと一緒にコンパイルする方法と、アスペクトだけコンパイルしておいて、実行時にウィービングする方法があります。

環境設定

環境変数CLASSPATH

AspectJが提供するコマンドを使用する場合は、環境変数CLASSPATHにAspectJのライブラリファイルを設定する必要があります。

D:\work> set CLASSPATH=C:\java\aspectj1.5\lib\aspectrt.jar
D:\work>

AspectJ言語の場合のコンパイル

アスペクトをコンパイルするときは、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アノテーションの場合のコンパイル

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
アスペクト・モジュールを収めたJARファイルの作成

META-INF/aop.xmlというAspectJ用のマニフェストファイルをJARファイル中に含めます。

aop.xml
<?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>
JavaVM実行時のオプション

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>は、クラスにフィールドやメソッドを追加したり、継承関係を変更します。

<join point>を選択する構文pointcut

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で指定可能な演算子には、&&、||、! があります。

<advice>を記述する

<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]");
    }

This page is written by Toru TAKAHASHI.(torutk@02.246.ne.jp)