アスペクト指向プログラミング
SpringBoot
などを利用していれば意図せずに利用しているアスペクト指向プログラミング。
今回は少々深掘りつつ、アスペクト指向プログラミングの代表的なライブラリ AspectJ
関連の仕組みを記載します。
アスペクト指向プログラミングとは?
wiki の抜粋ですが
- 横断的関心を実装する手法によって、プログラムのモジュール性を高めることを目的にしている
- オブジェクト指向ではうまく分離できない特徴(クラス間を横断するような機能)を「アスペクト」とみなす
- それをアスペクト記述言語を用いて分離して記述する
- するとプログラムに柔軟性が持たされる
ということらしいです。
上記でいう「クラス間を横断するような機能」は一般的にはロギングとかトランザクション制御とかでしょうか。
それらを「アスペクト」として切り出して、例えば DDD でいうところのドメインモデルと分離してプログラミングできると、
確かに柔軟性を得られるかなと思います。
AspectJ
Java でアスペクト指向プログラミングを実現するならば、大抵の場合は AspectJ
を利用するのではないでしょうか。
SpringBoot
も内部的には AspectJ
を利用しているはずです。
AspectJ の仕組みと用語
AspectJ
は
ポイントカットで選択されたジョインポイントにアドバイスが挿入される
ことで、AOP を実現しています。
アドバイス
Advice(アドバイス) は横断的に利用したい「処理」そのものです。メソッドとして記述することが多いと思います。
ポイントカット
Pointcut(ポイントカット) は Advice を適用する条件です。
AspectJ
では式で適用する条件を記述します。
ジョインポイント
JoinPoint(ジョインポイント) は Advice を適用するタイミングや場所のことです。
例えば、メソッド開始前や終了後などです。
実際のソースコード
Advice, Pointcut, JoinPoint を合わせて Aspect
として記述します。
記述方法は2種類あります。
class
ではなくaspect
として記述する- Java の通常のクラスにアノテーションを付与して記述する
最近はほとんどが後者の方法かと思いますので、後者の簡単なコードを記述してみます。
@Aspect
public class Aspect {
@Before("execution(* hoge.*.*(..))")
public void before() {
System.out.println("before!");
}
}
クラスに @Aspect
アノテーションを付与し、メソッド before
を Advice
として実装します。
この before
メソッドに @Before
アノテーションを付与することで、JoinPoint
の役割を果たすことができます。
@Before
の引数には Pointcut
式を定義します。
execution
という特有の記述方法で記載します。
execution(メソッド修飾子 メソッド戻り値 パッケージ名.クラス名.メソッド名(引数の型, 引数の型) throws 例外)
がフォーマットです。この式に該当するメソッドが実行される際にこのアスペクトが動作します。
Aspect はどのように適用されるのか?
ライブラリの効果により手順通りに記述すれば AOP
は簡単に実現できます。
しかし、あまり気にする人は少ないかと思いますが、どの様に Aspect
が対象のメソッドの実行前や実行後にもれなく動作するのでしょうか?
Weaving
Aspect
を適用することをウィービング(Weaving)と言います。本来の業務処理に横断的な処理を「編み込む」ということです。
このウィービングという作業を、いつ、どの様にやっているのか。それが Aspect
の適用方法になります。
compile-time weaving
コンパイル時に対象のコードに Aspect
を編み込む方法です。対象の業務クラスとAspect
を合わせた状態で class
ファイルを生成します。
post-compile weaving
コンパイル済みの業務コード(classファイル) にAspect
を編み込む方法です。
これは AOP
無しでビルド済みの jar
ファイルに後から Aspect
を編み込むことが可能な方法です。
load-time weaving
クラスファイルを JVM がロードする際に Aspect
を編み込む方式です。
実際の方法は上記の2種類と変わりないですが、タイミングが java プロセス起動時のクラスロードまで延期されただけです。
SpringBoot で利用している Weaving
上述の 3 種類の Weaving はタイミングが異なるだけで、基本的な方式は一緒です。
しかし、SpringBoot
が実施している方式はこれらとは異なります。
SprintBoot
は起動時に Spring コンテナに Bean
を登録すると思いますが、その際に「編み込まれる側のクラスのサブクラスを動的に生成して、そのインスタンスを登録する」ことで、Weaving を実現しています。
つまり、基本的な weaving とは違い class
ファイルに直接手を加えるわけではなく、サブクラス(これを Proxy と言います) を生成することで、AOP を実現しています。
そのため、SpringBoot の標準 AOP で Aspect を編み込むならば、対象のクラスは Spring の Bean として登録される必要があります。
@Component
が付与されているクラスが対象ということです。
Proxy の役割
サブクラスとして動的に生成される Proxy は、実際の処理の前後に Aspect
の Advice 処理を呼び出します。
例として @Before
として定義した Advice を呼び出すシーケンスを載せます。
上記を見ると「なぜ @Transactional
なメソッドは自クラスから呼び出すとトランザクション制御されないのか」が分かると思います。
それは、SpringBoot での Aspect 処理は Proxy を経由して始めて有効になるためです。
自クラス内でのメソッド呼び出しは Proxy を経由しないため、Aspect が実行されません。