前回のDIに続いてAOPについて学習します。
超入門的な内容になりますので、ご了承ください。
AOP(Aspect-Oriented Programming)
アスペクト志向プログラミングは、オブジェクト指向のような開発手法の概念の1つです。
メソッドを書いていると、どうしてもロギング処理や認証処理など、やりたい事と直接関係のない処理が混じりこんでしまいます。
そのような補助的な処理(関心事)を分離して記述しようという開発手法です。
例えば、以下のようなdrinkAlcohol()
メソッドがあるとします。
➀開始ログ:酒飲むよー!
↓
➁酒を飲む
↓
➂終了ログ:酔っぱらったよー!
この場合、①と③は「酒を飲む」行為とは直接関係ない処理なので、別の場所に分離して記述します。
そしてメソッド実行前に①を、実行後に③を実行するように設定します。
これでdrinkAlcohol()
メソッドはロギング処理を気にせず、②「酒を飲む」処理だけに集中すればよくなります。
このように補助的な処理(関心事)を分離してモジュール化する考え方をAOPと言います。
古いですが、ここの説明がわかりやすかったです。
用語説明
用語 | 意味 |
---|---|
Aspect | 共通処理(関心事)の振る舞いと、適用するポイントをまとめたもの = AdviceとPointcutをまとめたもの |
Advice | 分離した共通処理の振る舞い 実際に実行される処理そのもの |
JoinPoint | Adviceを入れるタイミング メソッドの実行前、実行後など |
PointCut | Adviceを適用する条件 JoinPointに達成した時、Adviceを実行するかどうか判定する条件式 |
JoinPointアノテーション
アドバイスの実行タイミングを設定するアノテーションが用意されています。
アノテーション | タイミング |
---|---|
@Before | メソッド実行前に実行される |
@After | メソッド実行後に実行される 実行結果は問わない |
@Around | メソッドの代わりに実行される。 メソッド前後の処理 |
@AfterReturning | メソッドが正常終了した場合に実行される |
@AfterThrowing | メソッドで例外が発生した場合に実行される |
PointCut指示子
アドバイスを実行する条件式を記載します。
一般的によく使われるexecution
のフォーマットを見てみます。
execution(メソッド修飾子 戻り値 パッケージ名.クラス名.メソッド名(引数の型) throws 例外)
メソッド修飾子、例外のスローは省略可能です。
@Before("execution(* com.sample..*(..))")
上記の例では、com.sample
パッケージ配下にあるメソッドの実行前に実行されます。
条件式にはワイルドカードが利用可能です。
ワイルドカード | 意味 |
---|---|
* | 任意の型、またはクラス名やパッケージ名の一部の代わり |
.. | 任意の引数、またはクラス名やパッケージ名の一部の代わり |
+ | クラス名、インターフェース名の右側に書くことで、サブクラスやインターフェースの実装を全て指定 |
execution
以外にも次のような指示子が用意されています。
PointCut指示子 | 実行条件 |
---|---|
within(クラス名) | 指定したクラスのメソッドに適用 |
target(クラス名) | 指定したクラスを継承したクラスのメソッドに適用(親クラス、子クラス両方に適用) |
args(引数の型) | 指定した引数と一致する引数を持つメソッドに適用 |
@annotation(アノテーション) | 指定したアノテーションが付いているメソッドに適用 |
サンプルコード
上記のdrinkAlcohol()
メソッドをAOPで実現してみました。
public class Drink {
public void drinkAlcohol() {
System.out.println("drinking...");
}
}
ロギング処理は書かずに、②酒を飲む処理だけ記述します。
@Aspect
@Component
public class SampleAspect {
@Before("execution(* com.sample..*(..))")
public void beforeDrinking() {
System.out.println("[@Before]start drinking alcohol!");
}
@After("execution(* com.sample..*(..))")
public void afterDrinking() {
System.out.println("[@After]I got drunk...zzZ");
}
}
クラスに@Aspect、@Componentアノテーションを付与します。
@Beforeが①開始ログ、@Afterが③終了ログになります。
実行結果は以下になります。
[@Before]start drinking alcohol!
drinking...
[@After]I got drunk...zzZ
ちゃんとメソッド前後にログが出力されていますね。
@Around
では、同じ処理を@Aroundで実現してみます。
@Aspect
@Component
public class SampleAspect {
@Around("execution(* com.sample..*(..))")
public void aroundDrinking(ProceedingJoinPoint pjp) {
System.out.println("[@Around]start drinking alcohol!"); // 全処理(➀開始ログ)
try {
pjp.proceed(); // メソッドを呼び出す(➁drinkAlcohol())
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("[@Around]I got drunk...zzZ"); // 後処理(➂終了ログ)
}
}
@Aroundは対象メソッドの代わりにAroundアドバイスが実行されます。
ProceedingJoinPoint.proceed()
でメソッドを呼び出すことができるので、その前後にロギング処理を入れる感じですね。
参考資料
SpringでAOP
ざっくりとSpringで使うAOPの解釈をする
Springのアスペクト指向プログラミング
Spring AOP - ぺーぺーSEのブログ