aspectjによるjava言語拡張 2012.09.07
TRANSCRIPT
ウルシステムズ株式会社http://www.ulsystems.co.jp
mailto:[email protected]
Tel: 03-6220-1420 Fax: 03-6220-1402
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
AspectJによるJava言語拡張
2012/9/7
講師役:近棟 稔
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
はじめに
2001年にXerox PARCのGregor KiczalesによってJava言語向けに「アスペクト指向プログラミング」が導入され、それと同時にAspectJが公開されました。(それ以前はLISPのdefadviceなどでAOP相当の機能が提供されていました)
AspectJは、登場から11年が経過した今でもデファクトスタンダードの1つとして広く利用され続けています。
この勉強会では、AspectJの基本的な使い方の紹介から始め、通常のAOPの概念を飛び越した使い方に至るまで、面白そうな部分とそれに必要な基礎知識に絞って、かいつまんで説明したいと思います。
1
ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 2
AspectJとAJDTを試してみる
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
Listに文字列をaddした時に、その引数を標準出力に出力する。
java.util.Listのadd操作に対してAspectJを適用した様子を示します。コンソールにaddの引数を表示します。
3
AspectJを適用する対象のコード AspectJのコード
helloworld
実行結果
AJDT(AspectJ Development Tools) というeclipseプラグインを導入すると、このように関連付けが分かるようになります。
Cross References
(どのアドバイスがどの部分に埋め込まれるかを一覧表示)
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
AJDTを用いたAspectJプロジェクトの作り方
AspectJプロジェクトを新規作成する場合は、プロジェクトの新規作成時にAspectJ Projectを選択します。
既存プロジェクトをAspectJプロジェクトにするためには、「.project」ファイルを以下のように書き換えます。また、AspectJのライブラリーである「aspectjrt.jar」をクラスパスに追加します。
4
ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 5
AspectJ早めぐり
(最低限のものに絞って説明します)
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
Aspectの基本構造
AspectJではコード片をプログラム上の様々な部分に織り込む(ウィービングする)ために、Aspectというモジュール(クラスのようなもの)を作ります。典型的な書式は以下のようなものです。
6
アドバイスの引数は典型的にJoinPointになります。必要なければ引数なしでも構いません。
Aroundポイントカットの場合のみ、ProceedingJoinPointを用い、その他のポイントカットの場合は、
JoinPointを用います。このJoinPointオブジェクトを用いることで、引数を受け取ったりすることが
可能です。
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
プログラム中にバイトコードを入れる場所=JoinPoint
AspectJはバイトコード変換を用いて後から色々な場所にコードを埋め込むことが出来ますが、埋め込むことが可能な場所を混乱を招かない範囲に絞ってあります。現在サポートされているJoinPointは以下の通りです。(Java言語の変化などによって増える可能性があります)
7
書式 意味
call(MethodPattern) 指定メソッドの呼び出し側に処理追加する
call(ConstructorPattern) 指定コンストラクターの呼び出し側に処理追加する
execution(MethodPattern) 指定メソッド自身に処理追加する
execution(ConstructorPattern) 指定コンストラクター自身に処理追加する
staticinitialization(TypePattern) 指定のクラスにstatic初期化ブロックを追加する
preinitialization(ConstructorPattern) 指定のコンストラクターに処理追加する
initialization(ConstructorPattern) 指定のコンストラクターに処理追加する
get(FieldPattern) 指定のフィールドの読み込みコードの前後に処理追加する
set(FieldPattern) 指定のフィールドの書き込みコードの前後に処理追加する
handler(TypePattern) 例外のcatch処理に処理追加する
adviceexecution() AspectJのアドバイスの実行に処理追加する
上記の書式を !, &&, ||, (pattern) などで組み合わせる事も可能です。
(書式中の TypePattern, FieldPattern, MethodPattern, ConstructorPattern については後述)
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
メソッドに対するcallとexecutionのJoinPointの例
callとexecutionがバイトコードを織り込む場所は、例えば以下の場所になります。
8
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
コンストラクターに対するcallとexecutionのJoinPointの例
コンストラクターに対してcallとexecutionが織り込む場所を以下に示します。
9
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
クラスやオブジェクトの初期化に関連するJoinPointの例
クラスやオブジェクトを初期化する際に指定可能なソースコード上のJoinPointを以下に示します。
initializationが織り込む場所は、コンストラクターに対してexecutionが織り込む場所と同一です。違いは、 executionはコンストラクター呼び出しがあるたびに素直に毎回実行されますが、initializationはオブジェクト生成に対して最初の1度のみ呼ばれる所が異なります。この性質により、オブジェクト数のカウントなどが可能になります。
10
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
フィールドの読み書きに関連するJoinPointの例
フィールドの読み書きに関連するJoinPointの例を以下に示します。
11
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
例外のcatchに対するJoinPoint
例外のcatchに対しても処理が可能です。
12
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
adviceに対するJoinPoint
JoinPointに付与するコードを「アドバイス」といいます。AspectJのアドバイスに対して後からコードを付与する(織り込む)事が可能です。
13
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
TypePattern, FieldPattern, MethodPattern, ConstructorPattern
AspectJでは、 Cross-cutting concern (横断的関心事)に対してまとめて処理(アドバイス)を追加可能にするために、特殊なワイルドカード表現により、複数のJoinPointを指定可能です。以下にEBNF記法に似た書式で簡潔に書いたものを示します(AspectJのマニュアルから抜粋)。大カッコ[]内は、省略可能な部分です。「...」は繰り返し記述可能なことを示しています。
14
IdPattern = 単なる文字列。「*」と「..」はワイルドカード。「*」はピリオドを除いた文字列にマッチするワイルドカード。「..」は「.*.」の意味。
ModifiersPattern = [ ! ] JavaModifier ... // public とかstaticとか
TypePattern = IdPattern [ + ] [ [] ... ] // 「+」は型互換のものすべて(連なる継承階層すべて)。[]は配列
| ! TypePattern // 「!」はJavaのものと同じ。「~ではないもの」
| TypePattern && TypePattern // 「&&」はJavaのものと同じ。「かつ」
| TypePattern || TypePattern // 「||」はJavaのものと同じ。「もしくは」
| ( TypePattern ) // 条件はカッコで囲める
ThrowsPattern = [ ! ] TypePattern , ... // 「! (否定)」を付けることも出来る
// 以下はフィールドの表現。[カッコ]内は省略可能な部分。(例:"int java.awt.Point.x"→"* java..Point.*"→"* Point.*")
FieldPattern = [ModifiersPattern] TypePattern [TypePattern . ] IdPattern
// 以下はメソッドの表現。引数に指定可能な「..」は、引数の個数や型をワイルドカードにしたい場合に利用。(例:"(* java.util.List.add(..)")
MethodPattern = [ModifiersPattern] TypePattern [TypePattern . ] IdPattern (TypePattern | ".." , ... ) [ throws ThrowsPattern ]
// 以下はコンストラクターの表現。MethodPatternとの違いは、戻り値を書かないことと、メソッド名は「new」固定とすること。
ConstructorPattern = [ModifiersPattern ] [TypePattern . ] new (TypePattern | ".." , ...) [ throws ThrowsPattern ]
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
JoinPointに織り込むコード=advice
JoinPointに織り込むコードの事を「アドバイス(advice)」といいます。アドバイスの種類には、以下のものがあります。
15
アドバイスの種類 意味
@Around
FilterやProxyやInterceptorのように、実際のメソッド呼び出しを一旦保留し、アドバイスの実装に制御を任せた後、proceedingJoinPoint.proceed()
の処理によって元々意図したメソッド呼び出しを行うものです。万能ですが、常に使えるわけではありません。[@Aroundが利用可能なもの]call, execution, staticinitialization, get, set, adviceexecution[@Aroundが利用できないもの]preinitialization, initialization, handler
@Before JoinPointの前にアドバイスを埋め込みます。
@AfterReturningJoinPointの後にアドバイスを埋め込みます。例外が発生しなかった場合のみ、アドバイスが呼ばれます。
@AfterThrowingJoinPointの後にアドバイスを埋め込みます。例外が発生した場合のみ、アドバイスが呼ばれます。
@AfterJoinPointの後にアドバイスを埋め込みます。例外が発生したかしなかったかにかかわらず、アドバイスが呼ばれます。finally相当です。
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
より詳しく勉強するためには:AspectJクイックリファレンスを読む
16
AspectJの配布サイトにあるマニュアルです。
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
The AspectJ Programming Guide を読む
AspectJのダウンロード同梱物に入っているマニュアルになります。やはり詳しいです。
17
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
日本語の書籍を読む
「アスペクト指向入門」は、中身としてAspectJを使ったアスペクト指向プログラミングの話が書かれていて、おすすめです。
18
http://www.amazon.co.jp/
ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 19
例:関数呼び出しのトレース
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
関数の実行をトレースしてみる
以下のフィボナッチ関数の動きをトレースしてみます。
20
実行結果
ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 21
例:メモ化
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
関数の実行結果をキャッシュして再利用する。(メモ化, Memoization)
フィボナッチ関数は、引数が大きくなると非常に遅くなります。fibonacci(43)は、おおよそ3秒程度かかります。fibonacci関数を高速化するためには、一度計算した結果をキャッシュするという戦略をとります。これは一般的に「メモ化(memoization)」と呼ばれます。元のfibonacci関数をメモ化を利用するように書き換えると以下のようになります。
メモ化を入れるとfibonacci(43)の処理速度は3ms程度になりました。しかし、このようなキャッシュの仕組みを入れるために、fibonacci関数の中に本質的ではないメモ化用の処理が入る結果になります。この本質的ではない処理を外部に取り出すために、AspectJが利用可能です。
22
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
関数の実行結果をキャッシュして再利用する。(メモ化, Memoization)
メモ化を実現するためには、以下の要件が必要になります。
計算結果のキャッシュをどこかに保持する
キャッシュに計算結果がある場合は、呼び出された処理を呼び出す代わりにキャッシュの値を返す。
このように、メソッド本体の呼び出し可否をコントロールするためには、AspectJの@Aroundを利用します。具体的には以下のようにします。
このメモ化アドバイスを適用すると、 fibonacci(43)は初回5msかかり、2回目以降の呼び出し時にはキャッシュが使われ、0.03ms程度で計算結果が返ってくるようになります。
23
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
AspectJとJava5のアノテーション
AspectJを用いると、ソースコードを変更することなく処理を変更することが可能です。しかし、メモ化の例で見たように、Aspect側にポイントカットを指定してしまうと、せっかく作ったAspectを再利用しにくくなります。これを回避する現代的な方法として、Java5のアノテーションを使う方法がよく使われます。
24
このように、@Memoアノテーションをメソッドに付けるだけでメモ化されるようにする
AspectJでアノテーションを利用する際のメリット・デメリット
メリット対象のメソッドがメモ化される事に気付きやすい(対象のソースコード上に出現している)。AspectJを知らない技術者であっても、アノテーションを付ければ良いことだけ知っていれば、機能を利用できる。
デメリットせっかく「横断的感心事」としてアスペクト内に処理を閉じ込めることが出来ていたのに、「どこに処理を入れるか」というコードがプログラム内に散在してしまう。
ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 25
自作アノテーションの作り方
ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 26
ソースコード上のあらゆる要素に対して、静的な情報を付加する事が可能になりました。情報を付加可能な要素は以下の通りです。
パッケージ (PACKAGE)
クラス, インターフェース, enum (TYPE)
フィールド (FIELD)
メソッド, コンストラクター (CONSTRUCTOR)
メソッドの引数 (PARAMETER)
ローカル変数 (LOCAL_VARIABLE)
典型的なアノテーションとして、「@Override」アノテーションがあります。
@Memoアノテーションのソースコードは、以下のとおりで、とても簡単です。
annotation
@Memoアノテーション
メソッドに付与可能であるということを示す
VM実行中も参照可能であることを示す。AspectJと組み合わせる時はRUNTIME固定。
アノテーションのキモは、「ターゲット」と「リテンション」。特にターゲットが重要!
ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 27
annotationは、コード上のさまざまな箇所に「小さな目印」を付けるという機能です。付加場所と、アノテーション情報の保持スコープ(リテンションポリシーと呼ばれる)の組み合わせによって、以下のバリエーションが存在します。
アノテーション情報の保持スコープ(リテンションポリシー)別の主な用途を考えると、以下のようにまとめられます。 SOURCE
@Overrideや@SuppressWarningsのように、コンパイラやeclipseのような開発環境に対する指示を与えるためのアノテーションに用いられます。
CLASS単体のjavaファイルだけにとどまらず、複数のjavaファイルにまたがってコンパイラーに指示を与えたい場合や、jarなどのライブラリの状態にした後にも保持しておきたいアノテーションに用いられます。
RUNTIME (AspectJ用に考える場合はこれにする)
実行時にも読み取り可能なアノテーションであるため、フレームワークに対する特別な指示を与えるために用いられます。実行時のアノテーションを用いているフレームワークとして、JUnit4やEJB3.0などがあります。
annotationの理解
リテンション
付加場所
SOURCE(ソースコードのみ)
CLASS(.classファイルにも保持)
RUNTIME
(実行時にも保持)
PACKAGE パッケージ
TYPEクラスインターフェースenum
FIELD フィールド
METHOD メソッド
CONSTRUCTOR コンストラクター
PARAMETER 引数
LOCAL_VARIABLE ローカル変数
コンパイル後は無くなってしまうannotation
情報
(ソースコード上にのみ存在)
.classファイルには保持されるが、実行時には無くなってしまうannotation
情報
リテンションポリシーのデフォルト値はCLASS
実行時にも保持される
annotation情報
リフレクションを用いて読み取り
可能
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
ふたたび@Memoアノテーション
メソッドに付与可能な@Memoアノテーションを作ってみます。
上記のように作成したアノテーションを使ってみます。
これで何が出来たか?
アノテーションは、そこに小さな印が付いているだけで、アノテーション自身は何もしません。
ただし、「アノテーションがそこにある」ことによって、AspectJはポイントカットの範囲を「*あの*アノテーションがついているもの」と指定することが可能になります。
これにより、フレームワークの提供者側は「このアノテーションが付いているもの全部にアドバイスを付ける」という実装ができますし、フレームワーク利用者は「このアノテーションを付ければフレームワークがうまくやってくれる」と思えるようになります。役割を分けやすいのです。
28
• メソッドに付与
• 実行時にも保持可能
• クラスに付与しようとすると、コンパイルエラーになっています。
• フィールドに付与してもコンパイルエラーです。
• メソッドに付与すると、ちゃんと付いてくれました。
→これが「ターゲット」の機能です。
コラム:なぜ「@」なのかアノテーションには先頭に@を付けます。なぜ@なのかは、書籍「プログラミング言語Java」に記載があります。Annotation Type →(各単語の先頭文字を取って)AT→「アット」と読める→「@」 だそうです。
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
もう少しアノテーションの続き:アノテーションに追加の情報を付与する
アノテーションに以下のように要素の値を渡すことも可能です。
このように値を渡せるようにするためには、以下のようにアノテーションを作成します。
上記のように、defaultキーワードにより、デフォルト値を持たせることが可能です。なお、持たせることが可能な型には制約があり、以下のものしか持たせる事が出来ません。
29
Tips
アノテーションの持つ要素が1つしかなく、かつ、valueという特殊な要素名を利用するようにアノテーションを設計した場合、@Performance(value = 123)と記述する代わりに@Performance(123)と記述することが可能になります。@SuppressWarnings("unchecked")などが典型例です。
intやlongなどのプリミティブデータ型, String, enum, アノテーション, クラス
およびこれらの配列
ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 30
ふたたび@Memoによるメモ化
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
アノテーションをたよりにポイントカットを選択する
@Memoアノテーションが付けられたメソッドに対してアドバイスを付けるように変更してみます。ただし、この実装にはまだ色々な課題があります。
1. キャッシュの生成消滅を利用者がコントロール可能にすべき。(キャッシュオブジェクトを特別に渡す)
2. マルチスレッド対応。(スレッドローカルを使うか、1の対応でカバー)
3. mapのキーがMemoizationKeyではちゃんと機能しない場合がある。(1の対応でカバー)
31
やろうとすると大掛かりになる
ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 32
自作「@TraceLog」によるログ出力
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
@TraceLogアノテーションでメソッドの呼び出しログ出力を表示する
@TraceLogを付ければログ書き出しをするものが以下のように作成できます。
33
execution(long pkg.Sample.fibonacci(int)) [1] calledexecution(long pkg.Sample.fibonacci(int)) [1] --> 1execution(long pkg.Sample.fibonacci(int)) [1] --> 1 (0.100942ms)
ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 34
コーディングルールの追加
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
AspectJでプロジェクト用コーディングルールを追加する
プロジェクトで禁止したいルールをAspectJでコンパイルエラーにすることも出来ます。この例では、publicフィールドを直接書き換えようとしたコードをエラーにしています。
35
このようなプロジェクト用コーディングルールを警告したりエラーにしたりする他の案としては、以下のような案が考えられます。
ある種のクラスでは、特定クラスや特定パッケージのクラスの呼び出しを禁止する。(例:Repositoryクラス以外でEntityManagerに触ることを禁止する)ある種のクラスでは、staticメソッドやstaticフィールドを作ってはいけない(作ってもいいけど使うとエラー)。
AspectJは元々コードチェッカーとして作られていないため、うまくコーディングルールをAspectJで作成できないパターンも多いですが、ポイントカットとして定義できる範囲であればこのような事が可能です。
ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 36
AspectJによる Dependency Injection
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
AspectJでのDI
AspectJを用いると、簡単なDIのようなものも作成可能です。ただし、DIを実装しているようなものなので、SpringやGuiceやCDIを素直に使ったほうがフルセットのDIが使えて良いと思います。
AspectJで作ったDIを使ってみているところ
37
DIの処理と、DIするクラスと、その生成ロジック
@DIフィールドには、ここの戻り値の型情報とマッチするオブジェクトをインジェクションするように作っています。なお、オブジェクトの生成単位を毎回newするか、シングルトンにするかはここの実装でコントロール可能です。ここではDI対象がnullだった場合、毎回newしています。
上記のようなDIは、対象フィールドを読み取るタイミングで依存関係を解決するため、起動が高速です。また、大掛かりなDIが不要な場合に、非常にシンプルにDIを実現できます。
ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 38
1つのクラスを分割したファイルに記述可能な inter type
declaration
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
inter type declaration (ITD) という機能
1つのクラスを構成するソースコードを、分割して記述可能です。このような機能を、AspectJではinter type declaration (ITD) と呼びます。
39
分割
人の手で書くところ 自動生成するところ
この方式のメリットは、generation gap パターンのようにスーパークラスが固定されないことです。デメリットは、オーバーライドが出来ないことです。
ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 40
JavaにおけるMixin(多重継承)の実現
ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
キャストが必須ですが、多重継承も実現可能です。
AspectJには@DeclareMixinというアノテーションもあり、これを用いるとキャストが必要になるものの、多重継承が可能になります。
41
1. インターフェースを作る
2. インターフェースの実装を作る
3. インターフェースの実装を織り込む場所を指定し、実装を織り込む
AspectJのMixinの使い所かなり限定的ですが、フレームワークのみが知っておけば良いメソッドや情報を、特定のクラスに保持させるために便利かもしれません。