AOP
AOP(アスペクト指向プログラミング)は、他の Bean のメソッドの前、後、または周囲で独自のコードを実行する仕組みです。対象の Bean を編集する必要はありません。
なぜ必要か
ログ出力、処理時間の計測、監査、権限チェック、リトライなど、多くのクラスに同時に関わる関心事があります。これらをすべてのメソッドに書くと、同じコードが繰り返され、本来の処理と混ざります。AOP では、そのコードを アスペクト に一度だけ書き、どのメソッドに適用するかを ポイントカット で指定します。
1 つのクラスにしか関係しないコードには AOP は不要です。そのクラスに書いてください。また AOP にはコストもあります。アドバイスが適用された Bean は生成されたプロキシに置き換えられ、Bean が自分自身を呼び出す場合はアドバイスが実行されません(プロキシ を参照)。
用語
| 用語 | 意味 | コード上の形 |
|---|---|---|
| アスペクト | 追加のコードを持つ Bean | @Aspect() と @Component() を付けたクラス |
| アドバイス | 追加のコード 1 つと、それが実行されるタイミング | @Before、@After、@AfterReturning、@AfterThrowing、@Around を付けたメソッド |
| ポイントカット | アドバイスを適用するメソッド | アノテーションに渡す文字列。例: '@annotation(Timed)' |
| ジョインポイント | 横取りされた 1 回の呼び出し | JoinPoint または ProceedingJoinPoint パラメータ |
| プロキシ | アドバイスを呼び出す生成済みのラッパー | ジェネレーターが書き出し、Bean の代わりに注入される |
セットアップ
AOP は spring_core_dart に含まれています。package:spring_core_dart/aop.dart、またはそれをエクスポートしている package:spring_core_dart/spring_core_dart.dart をインポートします。Dart には実行時リフレクションがないため、プロキシはビルド時に spring_core_dart_generator が書き出します。パッケージはこれに依存し、build_runner を実行する必要があります。
dependencies:
spring_core_dart: ^0.7.1
dev_dependencies:
build_runner: ^2.17.0
spring_core_dart_generator: ^0.7.1
dart run build_runner build
短い例
@Timed() を付けたすべてのメソッドの処理時間を計測します。
// lib/src/beans.dart
import 'package:spring_core_dart/spring_core_dart.dart';
import 'package:my_app/spring_gen/src/beans.spring.dart'; // written by the generator
// The module: every class in this directory and below is scanned.
@ComponentScan()
final BeanModule appBeans = $appBeans;
// An annotation is a plain class with a const constructor.
class Timed {
const Timed();
}
// The aspect: a bean whose @Around method runs instead of each
// method marked @Timed().
@Aspect()
@Component()
@Log()
class TimingAspect {
@Around('@annotation(Timed)')
Object? time(ProceedingJoinPoint call) {
final watch = Stopwatch()..start();
try {
return call.proceed(); // runs the real method
} finally {
log.info('${call.signature.name}: ${watch.elapsedMilliseconds} ms');
}
}
}
// An ordinary bean. It does not know about the aspect.
@Service()
class Search {
@Timed()
List<String> find(String text) => ['dart', 'spring'];
}
// bin/main.dart
import 'package:my_app/src/beans.dart';
import 'package:spring_core_dart/spring_core_dart.dart';
void main() {
final context = ApplicationContext.run([appBeans]);
// getBean returns the proxy, so the advice runs and logs "find: 0 ms".
context.getBean<Search>().find('d');
context.close();
}
追加の設定は不要です。コンテキストは @Aspect または Advisor Bean を見つけた時点で AOP を有効にします。
このセクションのページ
- @Aspect - アドバイスを持つ Bean の宣言、順序、インスタンスごとのアスペクト。
- @Pointcut とポイントカット式 - ポイントカットへの名前付けと、メソッドを選ぶ式の構文。
- アドバイス -
@Before、@After、@AfterReturning、@AfterThrowing、@Around。 - @DeclareParents - 一致する Bean のプロキシに追加のインターフェースを実装させる。
- JoinPoint - 横取りした呼び出しについてアドバイスが読める情報と
MethodInterceptor。 - プロキシ - プロキシの作られ方、自己呼び出し、コードによるアドバイザー、制限。
Spring (Java) では
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
class AppConfig {}
@Aspect
@Component
class TimingAspect {
@Around("@annotation(com.example.Timed)")
Object time(ProceedingJoinPoint call) throws Throwable {
long start = System.nanoTime();
try {
return call.proceed();
} finally {
log.info(call.getSignature().getName() + ": "
+ (System.nanoTime() - start) / 1_000_000 + " ms");
}
}
}
重要な違い:
- Spring は実行時にプロキシを作ります(JDK 動的プロキシまたは CGLIB)。ここではジェネレーターがビルド時にプロキシクラスを書き出すため、変更のたびに
build_runnerを実行する必要があります。 @EnableAspectJAutoProxyはありません。アスペクトまたはアドバイザーの Bean があれば十分です。- ポイントカット内の型名は単純名です(
com.example.TimedではなくTimed)。Dart にはパッケージがありません。 - Dart のゲッターとセッター(public フィールドの暗黙のものを含む)にもアドバイスを適用できます。