Weitere ähnliche Inhalte
Ähnlich wie AspectJによるJava言語拡張 2012.09.07 (20)
Mehr von Minoru Chikamune (9)
AspectJによるJava言語拡張 2012.09.07
- 2. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
はじめに
2001年にXerox PARCのGregor KiczalesによってJava言語向けに「アスペクト指向プログ
ラミング」が導入され、それと同時にAspectJが公開されました。(それ以前はLISPのdefadv
iceなどでAOP相当の機能が提供されていました)
AspectJは、登場から11年が経過した今でもデファクトスタンダードの1つとして広く利用さ
れ続けています。
この勉強会では、AspectJの基本的な使い方の紹介から始め、通常のAOPの概念を飛び
越した使い方に至るまで、面白そうな部分とそれに必要な基礎知識に絞って、かいつまん
で説明したいと思います。
1
- 3. ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 2
AspectJとAJDTを試してみる
- 4. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
Listに文字列をaddした時に、その引数を標準出力に出力する。
java.util.Listのadd操作に対してAspectJを適用した様子を示します。コンソールにadd
の引数を表示します。
3
AspectJを適用する対象のコード AspectJのコード
hello
world
実行結果
AJDT(AspectJ Development Tools) というeclipseプラグインを導入すると、このように関連付けが
分かるようになります。
Cross References
(どのアドバイスがどの部分に埋め込まれるかを一覧表示)
- 5. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
AJDTを用いたAspectJプロジェクトの作り方
AspectJプロジェクトを新規作成する場合は、プロジェクトの新規作成時にAspectJ Proje
ctを選択します。
既存プロジェクトをAspectJプロジェクトにするためには、「.project」ファイルを以下のよう
に書き換えます。また、AspectJのライブラリーである「aspectjrt.jar」をクラスパスに追加
します。
4
- 6. ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 5
AspectJ早めぐり
(最低限のものに絞って説明します)
- 7. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
Aspectの基本構造
AspectJではコード片をプログラム上の様々な部分に織り込む(ウィービングする)ために、
Aspectというモジュール(クラスのようなもの)を作ります。典型的な書式は以下のような
ものです。
6
アドバイスの引数は典型的にJoinPointになります。必要なければ引数なしでも構いません。
Aroundポイントカットの場合のみ、ProceedingJoinPointを用い、その他のポイントカットの場合は、
JoinPointを用います。このJoinPointオブジェクトを用いることで、引数を受け取ったりすることが
可能です。
- 8. 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 については後述)
- 9. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
メソッドに対するcallとexecutionのJoinPointの例
callとexecutionがバイトコードを織り込む場所は、例えば以下の場所になります。
8
- 10. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
コンストラクターに対するcallとexecutionのJoinPointの例
コンストラクターに対してcallとexecutionが織り込む場所を以下に示します。
9
- 11. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
クラスやオブジェクトの初期化に関連するJoinPointの例
クラスやオブジェクトを初期化する際に指定可能なソースコード上のJoinPointを以下に
示します。
initializationが織り込む場所は、コンストラクターに対してexecutionが織り込む場所と
同一です。違いは、 executionはコンストラクター呼び出しがあるたびに素直に毎回実
行されますが、initializationはオブジェクト生成に対して最初の1度のみ呼ばれる所が
異なります。この性質により、オブジェクト数のカウントなどが可能になります。
10
- 12. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
フィールドの読み書きに関連するJoinPointの例
フィールドの読み書きに関連するJoinPointの例を以下に示します。
11
- 13. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
例外のcatchに対するJoinPoint
例外のcatchに対しても処理が可能です。
12
- 14. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
adviceに対するJoinPoint
JoinPointに付与するコードを「アドバイス」といいます。AspectJのアドバイスに対して後
からコードを付与する(織り込む)事が可能です。
13
- 15. 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 ]
- 16. 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の前にアドバイスを埋め込みます。
@AfterReturning
JoinPointの後にアドバイスを埋め込みます。
例外が発生しなかった場合のみ、アドバイスが呼ばれます。
@AfterThrowing
JoinPointの後にアドバイスを埋め込みます。
例外が発生した場合のみ、アドバイスが呼ばれます。
@After
JoinPointの後にアドバイスを埋め込みます。
例外が発生したかしなかったかにかかわらず、アドバイスが呼ばれます。
finally相当です。
- 17. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
より詳しく勉強するためには:AspectJクイックリファレンスを読む
16
AspectJの配布サイトにあるマニュアルです。
- 18. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
The AspectJ Programming Guide を読む
AspectJのダウンロード同梱物に入っているマニュアルになります。やはり詳しいです。
17
- 19. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
日本語の書籍を読む
「アスペクト指向入門」は、中身としてAspectJを使ったアスペクト指向プログラミングの話
が書かれていて、おすすめです。
18
http://www.amazon.co.jp/
- 20. ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 19
例:関数呼び出しのトレース
- 21. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
関数の実行をトレースしてみる
以下のフィボナッチ関数の動きをトレースしてみます。
20
実行結果
- 23. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
関数の実行結果をキャッシュして再利用する。(メモ化, Memoization)
フィボナッチ関数は、引数が大きくなると非常に遅くなります。fibonacci(43)は、おおよそ3秒程度かかります。fib
onacci関数を高速化するためには、一度計算した結果をキャッシュするという戦略をとります。これは一般的に
「メモ化(memoization)」と呼ばれます。元のfibonacci関数をメモ化を利用するように書き換えると以下のようにな
ります。
メモ化を入れるとfibonacci(43)の処理速度は3ms程度になりました。しかし、このようなキャッシュの仕組みを入
れるために、fibonacci関数の中に本質的ではないメモ化用の処理が入る結果になります。この本質的ではない
処理を外部に取り出すために、AspectJが利用可能です。
22
- 24. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
関数の実行結果をキャッシュして再利用する。(メモ化, Memoization)
メモ化を実現するためには、以下の要件が必要になります。
計算結果のキャッシュをどこかに保持する
キャッシュに計算結果がある場合は、呼び出された処理を呼び出す代わりにキャッシュの値を返す。
このように、メソッド本体の呼び出し可否をコントロールするためには、AspectJの@Aroundを利用
します。具体的には以下のようにします。
このメモ化アドバイスを適用すると、 fibonacci(43)は初回5msかかり、2回目以降の呼び出し時に
はキャッシュが使われ、0.03ms程度で計算結果が返ってくるようになります。
23
- 25. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
AspectJとJava5のアノテーション
AspectJを用いると、ソースコードを変更することなく処理を変更することが可能です。しかし、メモ化
の例で見たように、Aspect側にポイントカットを指定してしまうと、せっかく作ったAspectを再利用し
にくくなります。これを回避する現代的な方法として、Java5のアノテーションを使う方法がよく使わ
れます。
24
このように、@Memoアノテーションをメソッドに付けるだけでメモ化されるようにする
AspectJでアノテーションを利用する際のメリット・デメリット
メリット
対象のメソッドがメモ化される事に気付きやすい(対象のソースコード上に出現している)。
AspectJを知らない技術者であっても、アノテーションを付ければ良いことだけ知っていれば、
機能を利用できる。
デメリット
せっかく「横断的感心事」としてアスペクト内に処理を閉じ込めることが出来ていたのに、「どこ
に処理を入れるか」というコードがプログラム内に散在してしまう。
- 26. ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 25
自作アノテーションの作り方
- 27. 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固定。
アノテーションのキモは、「ターゲット」と「リテンション」。特にターゲットが重要!
- 28. 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
情報
リフレクションを
用いて読み取り
可能
- 29. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
ふたたび@Memoアノテーション
メソッドに付与可能な@Memoアノテーションを作ってみます。
上記のように作成したアノテーションを使ってみます。
これで何が出来たか?
アノテーションは、そこに小さな印が付いているだけで、アノテーション自身は何もしません。
ただし、「アノテーションがそこにある」ことによって、AspectJはポイントカットの範囲を「*あの*アノテーション
がついているもの」と指定することが可能になります。
これにより、フレームワークの提供者側は「このアノテーションが付いているもの全部にアドバイスを付ける」
という実装ができますし、フレームワーク利用者は「このアノテーションを付ければフレームワークがうまく
やってくれる」と思えるようになります。役割を分けやすいのです。
28
• メソッドに付与
• 実行時にも保持可能
• クラスに付与しようとすると、コンパイルエラーになっています。
• フィールドに付与してもコンパイルエラーです。
• メソッドに付与すると、ちゃんと付いてくれました。
→これが「ターゲット」の機能です。
コラム:なぜ「@」なのか
アノテーションには先頭に@を付けます。なぜ@なのかは、書籍「プログラミング言語Java」に記載があります。
Annotation Type →(各単語の先頭文字を取って)AT→「アット」と読める→「@」 だそうです。
- 30. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
もう少しアノテーションの続き:アノテーションに追加の情報を付与する
アノテーションに以下のように要素の値を渡すことも可能です。
このように値を渡せるようにするためには、以下のようにアノテーションを作成します。
上記のように、defaultキーワードにより、デフォルト値を持たせることが可能です。なお、
持たせることが可能な型には制約があり、以下のものしか持たせる事が出来ません。
29
Tips
アノテーションの持つ要素が1つしかなく、かつ、valu
eという特殊な要素名を利用するようにアノテーション
を設計した場合、@Performance(value = 123)と記
述する代わりに@Performance(123)と記述すること
が可能になります。@SuppressWarnings("uncheck
ed")などが典型例です。
intやlongなどのプリミティブデータ型, String, enum, アノテーション, クラス
およびこれらの配列
- 31. ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 30
ふたたび@Memoによるメモ化
- 32. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
アノテーションをたよりにポイントカットを選択する
@Memoアノテーションが付けられたメソッドに対してアドバイスを付けるように変更してみます。た
だし、この実装にはまだ色々な課題があります。
1. キャッシュの生成消滅を利用者がコントロール可能にすべき。(キャッシュオブジェクトを特別に渡す)
2. マルチスレッド対応。(スレッドローカルを使うか、1の対応でカバー)
3. mapのキーがMemoizationKeyではちゃんと機能しない場合がある。(1の対応でカバー)
31
やろうとす
ると大掛か
りになる
- 33. ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 32
自作「@TraceLog」による
ログ出力
- 34. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
@TraceLogアノテーションでメソッドの呼び出しログ出力を表示する
@TraceLogを付ければログ書き出しをするものが以下のように作成できます。
33
execution(long pkg.Sample.fibonacci(int)) [1] called
execution(long pkg.Sample.fibonacci(int)) [1] --> 1
execution(long pkg.Sample.fibonacci(int)) [1] --> 1 (0.100942ms)
- 35. ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 34
コーディングルールの追加
- 36. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
AspectJでプロジェクト用コーディングルールを追加する
プロジェクトで禁止したいルールをAspectJでコンパイルエラーにすることも出来ます。こ
の例では、publicフィールドを直接書き換えようとしたコードをエラーにしています。
35
このようなプロジェクト用コーディングルールを警告し
たりエラーにしたりする他の案としては、以下のような
案が考えられます。
ある種のクラスでは、特定クラスや特定パッケージの
クラスの呼び出しを禁止する。(例:Repositoryクラス
以外でEntityManagerに触ることを禁止する)
ある種のクラスでは、staticメソッドやstaticフィールド
を作ってはいけない(作ってもいいけど使うとエラー)。
AspectJは元々コードチェッカーとして作られていない
ため、うまくコーディングルールをAspectJで作成でき
ないパターンも多いですが、ポイントカットとして定義
できる範囲であればこのような事が可能です。
- 37. ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 36
AspectJによる Dependency
Injection
- 38. 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を実現できます。
- 39. ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 38
1つのクラスを分割したファイルに記
述可能な inter type
declaration
- 40. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
inter type declaration (ITD) という機能
1つのクラスを構成するソースコードを、分割して記述可能です。このような機能を、Asp
ectJではinter type declaration (ITD) と呼びます。
39
分割
人の手で書くところ 自動生成するところ
この方式のメリットは、g
eneration gap パターン
のようにスーパークラス
が固定されないことです。
デメリットは、オーバーラ
イドが出来ないことです。
- 41. ULS Copyright © 2012 UL Systems, Inc. All rights reserved. 40
JavaにおけるMixin(多重継承)の
実現
- 42. ULS Copyright © 2012 UL Systems, Inc. All rights reserved.
キャストが必須ですが、多重継承も実現可能です。
AspectJには@DeclareMixinというアノテーションもあり、これを用いるとキャストが必要
になるものの、多重継承が可能になります。
41
1. インターフェースを作る
2. インターフェースの実装を作る
3. インターフェースの実装を織り込む場所を指定し、実装を織り込む
AspectJのMixinの使い所
かなり限定的ですが、フレームワークのみが知っ
ておけば良いメソッドや情報を、特定のクラスに保
持させるために便利かもしれません。