Springのアスペクト指向プログラミング


JavaのSpring FrameworkのDIを活用して、ロギングやセキュリティなどを関心事ごとに分離することを「アスペクト指向プログラミング(AOP)」と呼びます。このAOPを利用することで、インスタンスに外部から共通的な機能を入れ込むことができます。

出典:Spring徹底入門

🍮 AOPの用語

用語 説明
Aspect AOPの単位となる横断的な関心事を示すモジュール。ログ出力など
Join Point 横断的な関心事を実行するポイント。Springではメソッド実行時
Advice 横断的な実装を行う箇所
Pointcut 実行対象のJoin Pointを選択する表現(式)のこと
Weaving アプリケーションコードの適切なポイントにAspectを入れ込む処理のこと
Target AOP処理によって処理フローが変更されたオブジェクト

🐹 Springで利用可能なAdvice

Advice 概要
Before Join Pointの前に実行される
After Returning Join Pointが正常終了した後に実行される
After Throwing Join Pointで例外がスローされた後に実行される
After Join Pointの後に実行される
Around Join Pointの前後で実行される

出典:Spring徹底入門

😸 Spring AOPのサンプル

出典:Spring徹底入門

Spring AOPはAOPフレームワークのASpectJを使っています。依存関係は次のとおりです。

<dependency>
<groupId>org.springfraomework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springfraomework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.ASpectJ</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>

Aspectの実装例は次のとおりです。

@Aspect // Aspectであることを示す
@Component // DIコンテナにComponentとして登録
public class LoggingForMethodAspect {
@Before(execution(* *..*ServiceImpl.*(..))) // Before Adviceを示す。式で適用対象を設定
public void startLog(JoinPoint jp) {
System.out.println(Start Method: + jp.getSignature()); // 実行中のオブジェクト情報を取得
}
}

Java ConfigのAOPを利用するためのアノテーション@EnableAspectJAutoProxyを追加します。

この状態で実行してみます。

UserService userService = context.getBean(UserService.class);
userService.findOne(spring);
//=> Start Method: com.example.demo.UserServiceImpl.findOne(String)

🚌 Pointcut式

AspectJではJoin Pointの選択をexecute指示子で表現できます。

出典:Spring徹底入門

Pointcut式で利用可能なワイルドカードは次のとおりです。

ワイルドカード 説明
* 任意の文字列を表す。パッケージ表現なら任意のパッケージ位置階層を表す
.. パッケージなら任意の0階層以上のパッケージを表す
+ クラス名の後に指定することにより、そのクラスとそのサブクラス/実装クラスすべてを表す

以下はPointcut式のサンプルです。

  • execution(String com.example.user.UserSerice.*(..))
    • UserServiceクラスのStringが返り値のメソッドを対象とします
  • execution(* com.example.user.UserService.*(String, ..))
    • UserServiceクラスの1つ目の引数がStringのメソッドを対象とします

🤔 Adviceの対象オブジェクトや引数を取得

JoinPointgetTargetメソッドでオブジェクト(Proxy)を取得できます。getArgsメソッドで引数を取得できます。

@Around(execution(* *..*ServiceImpl.*(..)))
public Object log(JoinPoint jp) throws Throwable {
// 対象オブジェクトを取得
Object targetObject = jp.getTarget();
// 対象のProxyを取得
Object.thisObject = jp.getThis();
// 引数を取得
Object[] args = jp.getArgs();
}

🎉 Springで利用されているAOP

Springで利用されているAOPの利用例を紹介します。

@Transactional:トランザクション管理

@Transactionalアノテーションを付与することでメソッドが正常終了すればコミットされ、実行時例外が発生したらロールバックします。

@Transactional
public Reservation reserve(Reservation reservation) { /* 予約処理 */ }

@PreAuthorize:認可処理

@PreAuthorizeアノテーションでメソッドが呼ばれる前に特定の条件で認可されているか確認できます。

@PreAuthorize(hasRole("ADMIN"))
public User create(User user) {
// ユーザー登録処理 (Adminロールを持つユーザーだけ実行できる)
}

@キャッシュ処理:キャッシュ処理

@Cacheableアノテーションを付与することで、キー(引数など)のキャッシュがあればキャッシュを返すようにできます。

@Cachable(user)
public User findOne(String email) {
// ユーザー取得処理
}

@Async:非同期処理

@Asyncアノテーションを付与することで、時間のかかる処理を別スレッドで実行できます。

@Async
public CompletableFuture<Result> calc() {
Result result = doSomething(); // 時間のかかる計算処理
return CompletableFuture.completedFuture(result);
}

@Retry:リトライ処理

@Retryメソッドが正常終了するまでリトライ処理を行うことができます。

@Retryable(maxAttempts = 3)
public String callWebApi() { /* WEB API呼び出し */ }

🐯 参考リンク

📚 おすすめの書籍