アドバイスを宣言する
アドバイスはポイントカット式に関連付けられており、ポイントカットに一致するメソッド実行の前、後、または前後で実行されます。ポイントカット式は、インラインポイントカットまたは名前付きポイントカットへの参照のいずれかです。
Before アドバイス
@Before
アノテーションを使用して、アスペクトでアドバイスの前に宣言できます。
次の例では、インラインポイントカット式を使用しています。
Java
Kotlin
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("execution(* com.xyz.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@Aspect
class BeforeExample {
@Before("execution(* com.xyz.dao.*.*(..))")
fun doAccessCheck() {
// ...
}
}
名前付きポイントカットを使用する場合、前の例を次のように書き換えることができます。
Java
Kotlin
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("com.xyz.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@Aspect
class BeforeExample {
@Before("com.xyz.CommonPointcuts.dataAccessOperation()")
fun doAccessCheck() {
// ...
}
}
After Returning アドバイス
After returning アドバイスは、一致したメソッドの実行が正常に戻ったときに実行されます。@AfterReturning
アノテーションを使用して宣言できます。
Java
Kotlin
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning("execution(* com.xyz.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
@AfterReturning("execution(* com.xyz.dao.*.*(..))")
fun doAccessCheck() {
// ...
}
}
複数のアドバイス宣言(およびその他のメンバー)を、すべて同じアスペクト内に含めることができます。これらの例では、それぞれの効果に焦点を当てるために、1 つのアドバイス宣言のみを示しています。 |
場合によっては、返された実際の値にアドバイス本文でアクセスする必要があります。次の例に示すように、戻り値をバインドする @AfterReturning
の形式を使用して、そのアクセスを取得できます。
Java
Kotlin
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="execution(* com.xyz.dao.*.*(..))",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
@AfterReturning(
pointcut = "execution(* com.xyz.dao.*.*(..))",
returning = "retVal")
fun doAccessCheck(retVal: Any?) {
// ...
}
}
returning
属性で使用される名前は、advice メソッドのパラメーターの名前に対応している必要があります。メソッドの実行が戻ると、戻り値は対応する引数値としてアドバイスメソッドに渡されます。returning
句は、指定された型の値を返すメソッドの実行のみに一致を制限します(この場合、戻り値に一致する Object
)。
after returning アドバイスを使用する場合、まったく異なる参照を返すことはできないことに注意してください。
After Throwing アドバイス
After throwing アドバイスは、一致したメソッドの実行が例外をスローして終了したときに実行されます。次の例に示すように、@AfterThrowing
アノテーションを使用して宣言できます。
Java
Kotlin
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing("execution(* com.xyz.dao.*.*(..))")
public void doRecoveryActions() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing
@Aspect
class AfterThrowingExample {
@AfterThrowing("execution(* com.xyz.dao.*.*(..))")
fun doRecoveryActions() {
// ...
}
}
多くの場合、特定の型の例外がスローされたときにのみアドバイスを実行したい場合があります。また、アドバイス本体のスローされた例外へのアクセスも必要になることがよくあります。throwing
属性を使用して、一致を制限し(必要に応じて - そうでない場合は Throwable
を例外型として使用)、スローされた例外をアドバイスパラメーターにバインドできます。次の例は、その方法を示しています。
Java
Kotlin
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing(
pointcut="execution(* com.xyz.dao.*.*(..))",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing
@Aspect
class AfterThrowingExample {
@AfterThrowing(
pointcut = "execution(* com.xyz.dao.*.*(..))",
throwing = "ex")
fun doRecoveryActions(ex: DataAccessException) {
// ...
}
}
throwing
属性で使用される名前は、advice メソッドのパラメーターの名前に対応している必要があります。メソッドの実行が例外をスローして終了すると、例外は対応する引数値としてアドバイスメソッドに渡されます。throwing
句は、指定された型(この場合は DataAccessException
)の例外をスローするメソッドの実行のみに一致を制限します。
|
After (Finally) アドバイス
After (finally) アドバイスは、一致したメソッドの実行が終了すると実行されます。@After
アノテーションを使用して宣言されます。After アドバイスは、通常の戻り条件と例外の戻り条件の両方を処理するように準備する必要があります。通常、リソースの解放などに使用されます。次の例は、after finally アドバイスの使用方法を示しています。
Java
Kotlin
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
@After("execution(* com.xyz.dao.*.*(..))")
public void doReleaseLock() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.After
@Aspect
class AfterFinallyExample {
@After("execution(* com.xyz.dao.*.*(..))")
fun doReleaseLock() {
// ...
}
}
AspectJ の |
Around アドバイス
最後の種類のアドバイスはアドバイスに関するものです。Around アドバイスは、一致したメソッドの実行の「周り」で実行されます。メソッドの実行前と実行後の両方で作業を行い、いつ、どのように、メソッドが実際に実行されるかどうかを判断する機会があります。Around アドバイスは、メソッドの実行の前後でスレッドセーフな方法で状態を共有する必要がある場合によく使用されます。たとえば、タイマーの開始と停止などです。
要件を満たす最も強力でない形式のアドバイスを常に使用してください。 例: アドバイスがあなたのニーズに十分である場合は、アドバイスの周囲を使用しないでください。 |
Around アドバイスは、メソッドに @Around
アノテーションを付けることによって宣言されます。メソッドはその戻り型として Object
を宣言する必要があり、メソッドの最初のパラメーターは ProceedingJoinPoint
型である必要があります。アドバイスメソッドの本体内で、基になるメソッドを実行するために、ProceedingJoinPoint
で proceed()
を呼び出す必要があります。引数なしで proceed()
を呼び出すと、呼び出し元の元の引数が、呼び出されたときに基になるメソッドに提供されます。高度なユースケースでは、引数の配列(Object[]
)を受け入れる proceed()
メソッドのオーバーロードされたバリアントがあります。配列内の値は、呼び出されたときに基になるメソッドへの引数として使用されます。
Spring が採用したアプローチはより単純であり、プロキシベースの実行のみのセマンティクスによりよく一致します。Spring 用に記述された |
アラウンドアドバイスによって返される値は、メソッドの呼び出し元から見た戻り値です。例: 単純なキャッシングアスペクトは、キャッシュがある場合はキャッシュから値を返すか、ない場合は proceed()
を呼び出す(そしてその値を返す)ことができます。proceed
は、周囲のアドバイスの本文内で 1 回、何度も呼び出されるか、まったく呼び出されない可能性があることに注意してください。これらはすべて正当です。
アラウンドアドバイスメソッドの return 型を void として宣言すると、null は常に呼び出し元に返され、proceed() の呼び出しの結果は事実上無視されます。アラウンドアドバイスメソッドは Object の戻り値の型を宣言することをお勧めします。基礎となるメソッドが void の戻り型を持っている場合でも、advice メソッドは通常、proceed() の呼び出しから返された値を返す必要があります。ただし、アドバイスは、ユースケースに応じて、オプションでキャッシュされた値、ラップされた値、その他の値を返す場合があります。 |
次の例は、アラウンドアドバイスの使用方法を示しています。
Java
Kotlin
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("execution(* com.xyz..service.*.*(..))")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.ProceedingJoinPoint
@Aspect
class AroundExample {
@Around("execution(* com.xyz..service.*.*(..))")
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
// start stopwatch
val retVal = pjp.proceed()
// stop stopwatch
return retVal
}
}
アドバイスパラメーター
Spring は完全に型付けされたアドバイスを提供します。つまり、Object[]
配列を常に使用するのではなく、アドバイス署名で必要なパラメーターを宣言することを意味します(返り値とスローの例で前述)このセクションの後半で、アドバイス本体で引数やその他のコンテキスト値を使用できるようにする方法を確認します。最初に、アドバイスが現在アドバイスしている方法を知ることができる一般的なアドバイスを書く方法を見てみましょう。
現在の JoinPoint
へのアクセス
どのアドバイスメソッドも、最初のパラメーターとして、型 org.aspectj.lang.JoinPoint
のパラメーターを宣言できます。JoinPoint
のサブクラスである型 ProceedingJoinPoint
の最初のパラメーターを宣言するには、周りのアドバイスが必要であることに注意してください。
JoinPoint
インターフェースは、いくつかの便利な方法を提供します。
getArgs()
: メソッドの引数を返します。getThis()
: プロキシオブジェクトを返します。getTarget()
: ターゲットオブジェクトを返します。getSignature()
: アドバイスされているメソッドの説明を返します。toString()
: 推奨されている方法の有用な説明を出力します。
詳細については、javadoc (英語) を参照してください。
アドバイスにパラメーターを渡す
戻り値または例外値をバインドする方法についてはすでに説明しました(戻り値と after throwing アドバイスの後に使用)。引数値をアドバイス本文で使用できるようにするには、args
のバインディング形式を使用できます。args
式で型名の代わりにパラメーター名を使用すると、アドバイスが呼び出されたときに、対応する引数の値がパラメーター値として渡されます。例はこれをより明確にする必要があります。Account
オブジェクトを最初のパラメーターとして受け取る DAO 操作の実行をアドバイスしたいとし、アドバイス本文のアカウントにアクセスする必要があるとします。次のように書くことができます:
Java
Kotlin
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
public void validateAccount(Account account) {
// ...
}
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
fun validateAccount(account: Account) {
// ...
}
ポイントカット式の args(account,..)
部分には 2 つの目的があります。まず、メソッドが少なくとも 1 つのパラメーターを取り、そのパラメーターに渡される引数が Account
のインスタンスであるメソッド実行のみに一致を制限します。次に、account
パラメーターを介して、実際の Account
オブジェクトをアドバイスで利用できるようにします。
これを記述する別の方法は、Account
オブジェクト値がジョインポイントと一致するときにそれを「提供する」ポイントカットを宣言し、アドバイスから名前付きポイントカットを参照することです。これは次のようになります。
Java
Kotlin
@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
private fun accountDataAccessOperation(account: Account) {
}
@Before("accountDataAccessOperation(account)")
fun validateAccount(account: Account) {
// ...
}
詳細については、AspectJ プログラミングガイドを参照してください。
プロキシオブジェクト (this
)、ターゲットオブジェクト (target
)、およびアノテーション (@within
、@target
、@annotation
、@args
) はすべて、同様の方法でバインドできます。次の一連の例は、@Auditable
アノテーションが付けられたメソッドの実行を照合し、監査コードを抽出する方法を示しています。
以下は、@Auditable
アノテーションの定義を示しています。
Java
Kotlin
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Auditable(val value: AuditCode)
以下は、@Auditable
メソッドの実行に一致するアドバイスを示しています。
Java
Kotlin
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") (1)
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
1 | ポイントカット式の組み合わせで定義された publicMethod という名前のポイントカットを参照します。 |
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") (1)
fun audit(auditable: Auditable) {
val code = auditable.value()
// ...
}
1 | ポイントカット式の組み合わせで定義された publicMethod という名前のポイントカットを参照します。 |
アドバイスパラメーターとジェネリクス
Spring AOP は、クラス宣言およびメソッドパラメーターで使用されるジェネリクスを処理できます。次のようなジェネリクス型があるとします。
Java
Kotlin
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}
interface Sample<T> {
fun sampleGenericMethod(param: T)
fun sampleGenericCollectionMethod(param: Collection<T>)
}
メソッドをインターセプトするパラメーター型にアドバイスパラメーターを関連付けることにより、メソッド型のインターセプトを特定のパラメーター型に制限できます。
Java
Kotlin
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Advice implementation
}
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
fun beforeSampleMethod(param: MyType) {
// Advice implementation
}
このアプローチは、ジェネリクスコレクションでは機能しません。次のようにポイントカットを定義することはできません。
Java
Kotlin
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
// Advice implementation
}
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
fun beforeSampleMethod(param: Collection<MyType>) {
// Advice implementation
}
これを機能させるには、コレクションのすべての要素をインスペクションする必要がありますが、これは合理的ではありません。null
値の一般的な扱い方も決定できないためです。これに似た何かを実現するには、Collection<?>
にパラメーターを入力し、要素の型を手動で確認する必要があります。
引数名の決定
アドバイス呼び出しでのパラメーターのバインドは、ポイントカット式で使用される名前と、アドバイスおよびポイントカットメソッドシグネチャーで宣言されたパラメーター名との一致に依存します。
AspectJ API はパラメーター名を引数名として参照するため、このセクションでは引数とパラメーターという用語を同じ意味で使用します。 |
Spring AOP は、次の ParameterNameDiscoverer
実装を使用してパラメーター名を決定します。各発見者にはパラメーター名を発見する機会が与えられ、最初に成功した発見者が勝ちます。登録されたディスカバリのいずれもパラメーター名を判別できない場合は、例外がスローされます。
AspectJAnnotationParameterNameDiscoverer
対応するアドバイスまたはポイントカットアノテーションの
argNames
属性を介してユーザーが明示的に指定したパラメーター名を使用します。詳細については、明示的な引数名を参照してください。KotlinReflectionParameterNameDiscoverer
Kotlin リフレクション API を使用してパラメーター名を決定します。このディスカバーは、そのような API がクラスパスに存在する場合にのみ使用されます。
StandardReflectionParameterNameDiscoverer
標準の
java.lang.reflect.Parameter
API を使用してパラメーター名を決定します。javac
の-parameters
フラグを使用してコードをコンパイルする必要があります。Java 8+ で推奨されるアプローチ。AspectJAdviceParameterNameDiscoverer
ポイントカット式、
returning
、throwing
句からパラメーター名を推測します。使用されるアルゴリズムの詳細については、javadoc を参照してください。
明示的な引数名
@AspectJ アドバイスとポイントカットアノテーションには、オプションの argNames
属性があり、アノテーション付きメソッドの引数名を指定するために使用できます。
デバッグ情報がなくても @AspectJ アスペクトが AspectJ コンパイラー ( 同様に、@AspectJ アスペクトが |
次の例は、argNames
属性の使用方法を示しています。
Java
Kotlin
@Before(
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", (1)
argNames = "bean,auditable") (2)
public void audit(Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code and bean
}
1 | ポイントカット式の組み合わせで定義された publicMethod という名前のポイントカットを参照します。 |
2 | bean と auditable を引数名として宣言します。 |
@Before(
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", (1)
argNames = "bean,auditable") (2)
fun audit(bean: Any, auditable: Auditable) {
val code = auditable.value()
// ... use code and bean
}
1 | ポイントカット式の組み合わせで定義された publicMethod という名前のポイントカットを参照します。 |
2 | bean と auditable を引数名として宣言します。 |
最初のパラメーターの型が JoinPoint
、ProceedingJoinPoint
、または JoinPoint.StaticPart
の場合、argNames
属性の値からパラメーターの名前を省略できます。例: 前述のアドバイスを変更してジョインポイントオブジェクトを受け取る場合、argNames
属性にそれを含める必要はありません。
Java
Kotlin
@Before(
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", (1)
argNames = "bean,auditable") (2)
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code, bean, and jp
}
1 | ポイントカット式の組み合わせで定義された publicMethod という名前のポイントカットを参照します。 |
2 | bean と auditable を引数名として宣言します。 |
@Before(
value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", (1)
argNames = "bean,auditable") (2)
fun audit(jp: JoinPoint, bean: Any, auditable: Auditable) {
val code = auditable.value()
// ... use code, bean, and jp
}
1 | ポイントカット式の組み合わせで定義された publicMethod という名前のポイントカットを参照します。 |
2 | bean と auditable を引数名として宣言します。 |
型 JoinPoint
、ProceedingJoinPoint
、または JoinPoint.StaticPart
の最初のパラメーターに与えられる特別な処理は、他のジョインポイントコンテキストを収集しないアドバイスメソッドに特に便利です。このような状況では、argNames
属性を省略できます。例: 次のアドバイスでは、argNames
属性を宣言する必要はありません。
Java
Kotlin
@Before("com.xyz.Pointcuts.publicMethod()") (1)
public void audit(JoinPoint jp) {
// ... use jp
}
1 | ポイントカット式の組み合わせで定義された publicMethod という名前のポイントカットを参照します。 |
@Before("com.xyz.Pointcuts.publicMethod()") (1)
fun audit(jp: JoinPoint) {
// ... use jp
}
1 | ポイントカット式の組み合わせで定義された publicMethod という名前のポイントカットを参照します。 |
引数付きで続行
Spring AOP と AspectJ で一貫して動作する引数を使用して proceed
呼び出しを記述する方法を説明することを以前に述べました。解決策は、アドバイス署名が各メソッドパラメーターを順番にバインドするようにすることです。次の例は、その方法を示しています。
Java
Kotlin
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
"args(accountHolderNamePattern)") (1)
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
String accountHolderNamePattern) throws Throwable {
String newPattern = preProcess(accountHolderNamePattern);
return pjp.proceed(new Object[] {newPattern});
}
1 | 名前付きポイントカット定義の共有で定義された inDataAccessLayer という名前のポイントカットを参照します。 |
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
"args(accountHolderNamePattern)") (1)
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
accountHolderNamePattern: String): Any? {
val newPattern = preProcess(accountHolderNamePattern)
return pjp.proceed(arrayOf<Any>(newPattern))
}
1 | 名前付きポイントカット定義の共有で定義された inDataAccessLayer という名前のポイントカットを参照します。 |
多くの場合、このバインディングを行います(前の例のように)。
アドバイスのオーダー
複数のアドバイスがすべて同じジョインポイントで実行されるとどうなるでしょうか? Spring AOP は、AspectJ と同じ優先順位ルールに従って、アドバイスの実行順序を決定します。最も優先順位の高いアドバイスが最初に「途中」で実行されます(つまり、2 つの before アドバイスが与えられた場合、最も優先順位の高いアドバイスが最初に実行されます)。ジョインポイントから「途中」では、最も優先順位の高いアドバイスが最後に実行されます(したがって、2 つの after アドバイスが与えられた場合、最も優先順位の高いものが 2 番目に実行されます)。
異なるアスペクトで定義された 2 つのアドバイスが両方とも同じジョインポイントで実行する必要がある場合、特に指定しない限り、実行順序は定義されていません。優先順位を指定することにより、実行の順序を制御できます。これは、アスペクトクラスで org.springframework.core.Ordered
インターフェースを実装するか、@Order
アノテーションを付けて通常の Spring の方法で行われます。2 つのアスペクトを考えると、Ordered.getOrder()
から低い値(またはアノテーション値)を返すアスペクトの優先順位が高くなります。
特定のアスペクトの個別のアドバイス型はそれぞれ、概念的にはジョインポイントに直接適用することを目的としています。結果として、 Spring Framework 5.2.7 の時点で、同じ 同じ |