序文
Spring は Java 分野で現在最も人気のある開発フレームワークの 1 つであり、アスペクト指向プログラミングである AOP (Aspect Oriented Programming) をはじめ、便利で高速な機能を多数提供しています。この記事では、実際のアプリケーションにおける Spring AOP の実装原理、中心概念、ユースケースについて詳しく紹介します。
1. AOPの基本概念
1.1 エントリーポイント(ポイントカット)
ポイントカットは、どのメソッドがインターセプトされるかを記述する式であり、通常はメソッド名またはクラス名と一致する正規表現またはワイルドカードを使用します。Spring AOP は、静的ポイントカットと動的ポイントカットの 2 種類のポイントカットをサポートします。静的ポイントカットは作成時に決定されますが、動的ポイントカットは実際の条件に基づいて実行時に計算する必要があります。
1.2 アドバイス
アドバイスは、選択されたメソッドをインターセプトした後に AOP が実行するコードのブロックを指します。Spring AOP では、5 つの異なるタイプのアドバイスがサポートされています。
- Before Advice: ターゲットメソッドが実行される前に実行されます。
- アドバイス返却後:対象メソッドが正常終了した後に実行されます。
- スロー後アドバイス: ターゲット メソッドが例外をスローしたときに実行されます。
- After Final Advice: 例外が発生したかどうかに関係なく、ターゲット メソッドの完了後に実行されます。
- アラウンド アドバイス (Around Advice): ターゲット メソッドの実行をオーバーライドします。ターゲット メソッドを手動で呼び出す必要があります。
1.3 側面
アスペクトは、ポイントカットと通知を組み合わせたエンティティ オブジェクトであり、どのメソッドをいつインターセプトするかを記述し、実行する通知コード ブロックを指定するために使用されます。
2. Spring AOPの実装原理
Spring AOP の実装原理は、JDK ダイナミック プロキシまたは CGLIB バイトコード テクノロジに基づいています。ターゲット オブジェクト上に動的プロキシまたはサブクラスを作成し、必要なメソッドをインターセプトして対応する通知を実行し、実行後にターゲット オブジェクトに制御を渡します。このようにして、ターゲットのオブジェクト コードを変更することなく、ターゲット メソッドを拡張できます。
具体的には、Spring AOP インターセプト メソッドの実装は次のステップに分かれています。
- 側面を定義し、ポイントカットとアドバイスを構成します。
- ターゲット オブジェクト タイプに基づいてプロキシ オブジェクトを作成します。
- ポイントカットの要件を満たすメソッドをインターセプトし、構成されたアドバイス ブロックを実行します。
3. Spring AOP の実践ケース
Spring AOP の実装原理をよりよく理解するために、簡単な実際のケースを例として考えてみましょう: AOP を使用して、メソッドの実行、実行結果、例外情報などを含む各メソッド実行のログを出力します。
3.1 ポイントカットとアドバイスを定義する
まず、ポイントカットを定義する必要があります。ここでは、便宜上、すべてのパブリック メソッドが直接一致します。
@Pointcut("execution(public * *(..))")
public void logPointCut() {
}
次に、ターゲット メソッドの実行前後にログを出力するサラウンド アドバイスを定義します。
@Around("logPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
String args = Arrays.toString(joinPoint.getArgs());
if (result instanceof Serializable) {
log.info("{}#{}({}, {}) : {} , 耗时 {} ms", className, methodName, args, result, duration);
} else {
log.info("{}#{}({}, [not serializable object]) , 耗时 {} ms", className, methodName, args, duration);
}
return result;
}
上記のコードでは、@Around
アノテーションは、メソッドが周囲のアドバイスであり、logPointCut()
という名前の。ProceedingJoinPoint
type はインターセプトする必要のある接続ポイントを示し、そのproceed()
メソッド対象のメソッドを実行し続けることができ、戻り値の型は対象メソッドの実行結果である Object になります。
3.2 AOP の構成
次に、Spring 構成ファイルで AOP を構成して、アスペクト、ポイントカット、通知をバインドする必要があります。
<!-- 定义切面 -->
<bean id="logAspect" class="com.example.LogAspect"/>
<!-- 配置 AOP -->
<aop:config>
<aop:aspect ref="logAspect">
<!-- 声明切入点 -->
<aop:pointcut id="logPointCut" expression="execution(public * *(..))"/>
<!-- 声明通知 -->
<aop:around method="around" pointcut-ref="logPointCut"/>
</aop:aspect>
</aop:config>
上記のコードでは、<bean>
タグはlogAspect
という名前の、そのクラスを として指定しcom.example.LogAspect
、アスペクト オブジェクトを定義します。<aop:config>
このタグは、AOP 構成の開始を示します。この<aop:aspect>
構成では、ref
プロパティをlogAspect
指す、つまり以前に定義されたアスペクト オブジェクトを参照するアスペクトを定義します。プロパティ<aop:pointcut>
が前の定義に従って使用されるエントリ ポイントを宣言します。また、周囲のオブジェクトを宣言します。メソッド名は として指定され、以前に定義されたポイントカットにバインドされます。expression
<aop:around>
around
3.4 SpringBoot版のAOPの場合
注: Spring Boot で AOP を使用するには、AOP アスペクトを使用できるように、@EnableAspectJAutoProxy アノテーションをスタートアップ クラスの構成に追加して自動プロキシ機能を有効にする必要があります。
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(public * *(..))")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
String args = Arrays.toString(joinPoint.getArgs());
if (result instanceof Serializable) {
log.info("{}#{}({}, {}) : {} , 耗时 {} ms", className, methodName, args, result, duration);
} else {
log.info("{}#{}({}, [not serializable object]) , 耗时 {} ms", className, methodName, args, duration);
}
return result;
}
}
3.5 テストアプリケーション
最後に、ビジネス コードにいくつかのメソッドを追加してテストします。
@Service
public class MyService {
public void sayHello(String name) {
System.out.println("Hello, " + name + "!");
}
public int divide(int a, int b) {
return a / b;
}
}
上記のコードは、MyService
という名前の。このクラスには、挨拶の印刷と分割操作という 2 つの単純なメソッドが含まれています。ここで、サービス クラスを Spring コンテナに挿入し、そのメソッドを呼び出すだけで、AOP は対応するログ情報をコンソールに出力します。
MyService myService = context.getBean(MyService.class);
myService.sayHello("World");
myService.divide(10, 2);
myService.divide(10, 0);
コードの最初の行では、Spring コンテナからサービス クラスのインスタンス オブジェクトを取得し、 と メソッドを順番に呼び出しますsayHello
。divide
コンソールに次のようなログ情報が出力されることがわかります。
com.example.MyService#sayHello([World], null) : null , 耗时 0 ms
com.example.MyService#divide([10, 2], 5) : 2 , 耗时 0 ms
com.example.MyService#divide([10, 0], java.lang.ArithmeticException: / by zero) , 耗时 1 ms
4. まとめ
Spring AOPは、プロキシやバイトコード技術を介してターゲットメソッドを傍受・通知することで、ターゲットメソッドの拡張を実現する、非常に便利で実用的なアスペクト指向プログラミング技術です。実際のアプリケーションでは、通常、ポイントカット、通知、およびアスペクトを使用して AOP の中核概念を記述し、構成ファイルを介してこれらを組み合わせて特定の機能を実現します。