カスタムSpring AOPアノテーションの実装
1. 前書き
この記事では、SpringのAOPサポートを使用してカスタムAOPアノテーションを実装します。
まず、AOPの概要を説明し、AOPとは何かとその利点について説明します。 これに続いて、アノテーションを段階的に実装し、AOPの概念を徐々に深く理解していきます。
その結果、AOPの理解が深まり、将来的にカスタムSpringアノテーションを作成できるようになります。
2. AOPアノテーションとは何ですか?
簡単に要約すると、AOPはアスペクト指向プログラミングの略です。 基本的に、it is a way for adding behavior to existing code without modifying that codeです。
この記事で実装するAOPのタイプは、注釈駆動型です。 Spring@Transactionalアノテーションを使用したことがあれば、これについてはすでにご存知かもしれません。
@Transactional
public void orderGoods(Order order) {
// A series of database calls to be performed in a transaction
}
The key here is non-invasiveness.アノテーションメタデータを使用することで、コアビジネスロジックがトランザクションコードで汚染されることはありません。 これにより、推論、リファクタリング、および単独でのテストが容易になります。
Springアプリケーションを開発している人は、それがどのように機能するかについてあまり詳しく考えなくても、これを‘Spring Magic 'と見なすことがあります。 実際には、何が起こっているのかは特に複雑ではありません。 ただし、この記事の手順を完了すると、AOPを理解して活用するために、独自のカスタムアノテーションを作成できるようになります。
3. メーベン依存
まず、Maven dependenciesを追加しましょう。
この例では、Spring Bootを使用します。これは、設定より規約により、できるだけ早く起動して実行できるためです。
org.springframework.boot
spring-boot-starter-parent
1.5.2.RELEASE
org.springframework.boot
spring-boot-starter-aop
アスペクトの実装を開始するために必要なライブラリをプルするAOPスターターが含まれていることに注意してください。
4. カスタムアノテーションの作成
作成するアノテーションは、メソッドの実行にかかる時間を記録するために使用されるアノテーションです。 アノテーションを作成しましょう:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
比較的単純な実装ですが、2つのメタアノテーションが何に使用されているかは注目に値します。
@Targetアノテーションは、アノテーションが適用される場所を示します。 ここではElementType.Method,を使用しています。これは、メソッドでのみ機能することを意味します。 他の場所で注釈を使用しようとすると、コードのコンパイルに失敗します。 この動作は、メソッドの実行時間を記録するために注釈が使用されるため、理にかなっています。
また、@Retentionは、実行時にJVMでアノテーションを使用できるかどうかを示しています。 デフォルトではそうではないため、Spring AOPは注釈を見ることができません。 これが再構成された理由です。
5. アスペクトの作成
これでアノテーションができました。アスペクトを作成しましょう。 これは、横断的な関心事をカプセル化するモジュールに過ぎません。これは、メソッド実行時間のロギングです。 @Aspect:で注釈が付けられたクラスだけです
@Aspect
@Component
public class ExampleAspect {
}
クラスも検出されるSpringBeanである必要があるため、@Componentアノテーションも含めました。 基本的に、これはカスタムアノテーションを挿入するロジックを実装するクラスです。
6. ポイントカットとアドバイスの作成
それでは、ポイントカットとアドバイスを作成しましょう。 これは、私たちの側面に存在する注釈付きメソッドになります。
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}
技術的には、これはまだ何の動作も変更しませんが、分析が必要なことはまだたくさんあります。
まず、メソッドに@Around.アノテーションを付けました。これはアドバイスです。アドバイスの周りは、メソッドの実行の前後にコードを追加することを意味します。 beforeやafterなど、他の種類のアドバイスもありますが、これらはこの記事の範囲外になります。
次に、@Aroundアノテーションにはポイントカット引数があります。 私たちのポイントカットは、「@LogExecutionTimeで注釈が付けられているすべてのメソッドにこのアドバイスを適用してください」と言っています。他にも多くの種類のポイントカットがありますが、スコープがある場合は再び除外されます。
メソッドlogExecutionTime()自体が私たちのアドバイスです。 単一の引数ProceedingJoinPoint.があります。この場合、これは@LogExecutionTime.で注釈が付けられた実行メソッドになります。
最後に、注釈付きメソッドが呼び出されると、アドバイスが最初に呼び出されます。 次に何をするかを決めるのは私たちのアドバイス次第です。 私たちの場合、私たちのアドバイスは、元のアノテーション付きメソッドを呼び出すだけのproceed(),を呼び出すこと以外は何もしていません。
7. 実行時間を記録する
これでスケルトンが準備できました。あとは、アドバイスにロジックを追加するだけです。 これは、元のメソッドの呼び出しに加えて、実行時間を記録するものです。 この追加の動作をアドバイスに追加しましょう。
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return proceed;
}
ここでも、特に複雑なことは何もしていません。 現在の時刻を記録し、メソッドを実行してから、コンソールにかかった時間を出力しました。 また、joinpointインスタンスを使用するために提供されているメソッドシグネチャもログに記録しています。 メソッド引数など、必要に応じて他の情報にアクセスすることもできます。
それでは、メソッドに@LogExecutionTime,アノテーションを付けてから実行して、何が起こるかを見てみましょう。 正しく動作するには、これがSpring Beanでなければならないことに注意してください。
@LogExecutionTime
public void serve() throws InterruptedException {
Thread.sleep(2000);
}
実行後、コンソールに次のログが記録されるはずです。
void org.example.Service.serve() executed in 2030ms
8. 結論
この記事では、Spring Boot AOPを利用してカスタムアノテーションを作成しました。これをSpring Beanに適用して、実行時に追加の動作を注入できます。
このアプリケーションのソースコードはover on GitHubで入手できます。これは、そのまま実行できるはずのMavenプロジェクトです。