使用 cglib を使用して簡単な AOP モジュールを実装します。
around にはまだ問題があります。
jar パッケージ#
jar パッケージcglib-nodep-2.1_3
が必要です。または、Maven に以下を追加します。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
バージョンは状況に応じて変更してください。
ターゲットクラス#
次のようなターゲットクラスがあると仮定します。
public class TestTarget {
public void out(String a) {
System.out.println("Target out."+a);
}
}
TestTarget クラスにはout
メソッドがあり、out
メソッドが実行される際にログを出力したいとします。
エンハンサー#
エンハンサー Enhancer は既存のメソッドを強化し、新しいオブジェクトを返します。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TestTarget.class);
enhancer.setCallback(callback);
コードは enhancer を作成し、Class<TestTarget>
オブジェクトを渡します。enhancer がそれに対して何を行うかは一旦無視します。
その後、callback を渡します。callback は次のようになります。
public class BeforeCallBack implements MethodInterceptor {
public BeforeCallBack() {
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// ここで何かを実行します。
return methodProxy.invokeSuper(o, objects);
}
}
BeforeCallBack
はMethodInterceptor
インターフェースを実装しています。ここで実際の強化が行われます。元のメソッドを返す前に、何かしらの処理を実行します(ここでは before ポイントカットですが、after や around に変更することもできます)。
効果:
ここでは、匿名内部クラスを使用してインターフェースを実装し、ラムダ式に置き換えます。
public class MainClass {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TestTarget.class);
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
System.out.println("before.");
return methodProxy.invokeSuper(o,objects);
});
TestTarget testTarget = (TestTarget) enhancer.create();
testTarget.out();
}
}
class TestTarget{
public void out(){
System.out.println("out. ");
}
}
before.
out.
汎用化#
- ポイントカット
強化の際に、どのメソッドが強化されるかを指定していませんので、クラス内のすべてのメソッドに影響を与えます。
cglib はCallbackFilter
を提供しており、どのメソッドにどの Callback(before、after など)を使用するかを区別することができます。
メソッドの種類をマークするために、アノテーション@interface
が最適です。
- アスペクト
メソッドをインターセプトした後、戻り値の前後でいくつかの操作を行います。intercept
メソッドに必要な操作を記述します。
つまり、Callback の要件があるたびに、インターフェースを手動で実装し、ビジネスコードを追加する必要があります。そのため、ビジネスコードを抽出する必要があります。
ビジネスコードを切り出すために、ビジネスを抽象化する必要があります。例えば、ログを出力する場合、LogAspect という抽象アスペクトを作成し、AbstractAspect を継承します。その後、intercept
メソッドで抽象メソッドを実行するだけです。
例:
public class BeforeCallBack implements MethodInterceptor {
Class<? extends AbstractAspect> aspect;
public BeforeCallBack(Class<? extends AbstractAspect> aspect) {
this.aspect = aspect;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
AbstractAspect instance = aspect.newInstance();
instance.doBefore();
return methodProxy.invokeSuper(o, objects);
}
}
実際のアスペクトが何であるかに関係なく、それはAbstractAspect
です。
- アノテーション解析
主要なアノテーションは 2 つあります:Aspect
、PointCut
。Aspect
は TargetClass にどのアスペクトを適用するかを示すために使用され、PointCut
はメソッドのポイントカットタイプ(before、after など)を示すために使用されます。