やること
JakartaEEのCDIインターセプタを用いてメソッドの前後にメッセージを吐き出す処理を追加する。
アスペクト指向プログラミング(AOP)とは
アスペクト指向プログラミング(Aspect Oriented Programming)とは、横断的関心(複数の場所から呼び出される共通的な処理)を実装する手法によりプログラムのモジュール性を高めることらしいです。
メソッドの前後にカットインしてログを取得したり、メッセージを吐き出したりする処理が横断的関心に当たるのかなと思います。
CDIインターセプタとは
JakartaEEでビジネスロジックを開発する際のフレームワークであるCDIの機能であり、メソッドの前後に共通的な処理をカットインするための仕組みです。
JakartaEEの標準機能を使ってAOPを実現するためには、CDIインターセプタを使えば良いとのこと。
※CDIインターセプタでAOPを実現するためには、対象はCDIで管理されたBeanである必要があります。
(AspectJというOSSを使ってAOPを実現する方法もありますが、なかなかうまくいかなかったため断念しました、、、)
手順
①アプリケーションを作成
②カスタムアノテーションを作成
③インターセプタを作成
④カスタムアノテーションを、カットイン処理したいメソッドに付与
①アプリケーションを作成
まずはCDIを使用してアプリケーションを作成します。
今回は下記リソースクラスとサービスクラスで動作するJAX-RS製REST APIを作りました。
SampleResource.java
@Path("api")
@RequestScoped
public class SampleResource {
@Inject
private SampleService service;
@Path("sampleresource")
@GET
@Produces(MediaType.TEXT_PLAIN)
public String sampleResource() {
return service.sampleService();
}
}
SampleService.java
@RequestScoped
public class SampleService {
public String sampleService() {
return "Hello CDIInterceptor!";
}
}
②カスタムアノテーションを作成
次にCDIインターセプタを有効化するためのカスタムアノテーションを作成します。
IntrceptorAnnotation.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import jakarta.interceptor.InterceptorBinding;
//カスタムアノテーション
@Inherited
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface InterceptorAnnotation {
}
③インターセプタを作成
次に、メソッドの前後に挟み込むインターセプタを作成します。
import jakarta.annotation.Priority;
import jakarta.interceptor.AroundInvoke;
import jakarta.interceptor.Interceptor;
import jakarta.interceptor.InvocationContext;
//インターセプタクラス
@Interceptor
//インターセプタの優先順位を定義
@Priority(Interceptor.Priority.APPLICATION)
@InterceptorAnnotation
public class SampleInterceptor {
@AroundInvoke
public Object obj(InvocationContext ic){
Object result = null;
//メソッド名取得
String methodName = ic.getMethod().getName();
try {
//メソッド実行前の処理
System.out.println(methodName + " 実行開始");
//メソッドの実行
result = ic.proceed();
//メソッドの実行後(正常終了時)
System.out.println(methodName + " 実行終了");
return result;
}catch(Exception e) {
//エラー発生時
System.out.println(methodName + " 実行中にエラーが発生");
return result;
}
}
}
上から順に確認していきます。
まずは、クラスに付与されたアノテーションに関して。
//インターセプタクラス
@Interceptor
//インターセプタの優先順位を定義
@Priority(Interceptor.Priority.APPLICATION)
@InterceptorAnnotation
public class SampleInterceptor {
@Interceptorにより、本クラスがインターセプタクラスであることを示します。
また、@Priority(Interceptor.Priority.APPLICATION)はインターセプタの優先順位を定義します。
(複数インターセプタが存在する際、どのインターセプタを実行するかを決定するみたいです。)
@InterceptorAnnotationは先ほど作成したカスタムアノテーションです。
次に、メソッドに付与されたアノテーションとメソッドの定義に関して。
@AroundInvoke
public Object obj(InvocationContext ic){
@AroundInvokeが付与されたメソッドにカットインする処理の内容を記述します。
また、メソッドは可視性:public、戻り値:Object型、引数:InvocationContext型で定義します。
次に、メソッドの中身に関して。
Object result = null;
//メソッド名取得
String methodName = ic.getMethod().getName();
try {
//メソッド実行前の処理
System.out.println(methodName + " 実行開始");
//メソッドの実行
result = ic.proceed();
//メソッドの実行後(正常終了時)
System.out.println(methodName + " 実行終了");
return result;
}catch(Exception e) {
//エラー発生時
System.out.println(methodName + " 実行中にエラーが発生");
return result;
}
InvocationContextオブジェクトのproceedメソッドの前にメソッド実行前処理を、後にメソッド実行後処理を記述します。
④カスタムアノテーションを、カットイン処理したいメソッドに付与
最後にカットイン処理をしたいメソッドにカスタムアノテーションを付与します。
以下のようにクラスに付与することで、そのクラス全部のメソッドにカットイン処理が適用されます。
@Path("api")
@RequestScoped
//本クラスの全てのメソッドにインターセプタを適用
@InterceptorAnnotation
public class SampleResource {
@Inject
private SampleService service;
@Path("sampleresource")
@GET
@Produces(MediaType.TEXT_PLAIN)
public String sampleResource() {
return service.sampleService();
}
}
また、以下のようにメソッドに付与することで、カットイン処理を挟みたいメソッドにのみ適用することができます。
@RequestScoped
public class SampleService {
@InterceptorAnnotation
//本メソッドにインターセプタを適用
public String sampleService() {
return "Hello CDIInterceptor!";
}
}
実行結果
以下のような実行結果がコンソールに吐き出されました。
sampleResource 実行開始
sampleService 実行開始
sampleService 実行終了
sampleResource 実行終了
リソースクラスのメソッドを実行→サービスクラスのメソッドの実行→サービスクラスのメソッドの正常終了→リソースクラスのメソッドの正常終了という流れでメソッドの前後にカットイン処理が行われたことを確認できました。
ソースコード
上記サンプルコードはGithubにコミットしています。