Spring AOPの基本概念と用語
SpringフレームワークにおけるAOP(Aspect-Oriented Programming)は、ビジネスロジックから横断的関心事を分離し、コードの保守性と再利用性を高めるための強力な機能です。実装に入る前に、AOPで頻出する専門用語の定義を整理します。
- 関心事(Concerns):アプリケーションが解決しようとする特定の課題や興味の対象。
- 横断的関心事(Cross-cutting Concerns):複数のモジュールやレイヤーにまたがって共通する関心事。ロギング、セキュリティ、トランザクション管理などが該当します。
- アスペクト(Aspect):横断的関心事をモジュール化したもの。Springでは通常、アノテーションが付与されたクラスとして定義され、ポイントカットとアドバイスを含みます。
- ジョインポイント(JoinPoint):プログラム実行中の特定の時点。メソッドの呼び出しや例外のスローなどが該当し、Spring AOPではメソッド実行が主なジョインポイントとなります。
- ポイントカット(Pointcut):アドバイスを適用するジョインポイントを絞り込むための条件式。
- アドバイス(Advice):特定のジョインポイントで実行される具体的な処理(コード)。事前(Before)、事後(After)、返戻後(AfterReturning)、例外発生時(AfterThrowing)、周囲(Around)などの種類があります。
- ターゲットオブジェクト(Target):アドバイスが適用される対象のオブジェクト。
- ウィービング(Weaving):アスペクトをターゲットオブジェクトに適用し、プロキシオブジェクトを生成するプロセス。
アノテーション駆動のアスペクト実装
Spring Boot環境では、@Aspect アノテーションを使用した宣言的なアプローチが標準的です。以下は、メソッドの実行時間を計測するためのカスタムアノテーションとアスペクトの実装例です。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 実行時間計測の対象となるメソッドに付与するカスタムアノテーション
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackExecutionTime {
}
/**
* 実行時間を計測するアスペクト定義
*/
@Aspect
@Component
public class ExecutionTimeMonitor {
/**
* @TrackExecutionTime が付与されたメソッドをインターセプトする
*
* @param pjp ジョインポイントのコンテキスト情報
* @return ターゲットメソッドの戻り値
* @throws Throwable ターゲットメソッドで発生した例外
*/
@Around("@annotation(TrackExecutionTime)")
public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
try {
// ターゲットメソッドの実行
return pjp.proceed();
} finally {
long duration = System.currentTimeMillis() - startTime;
System.out.printf("[Monitor] Method '%s' executed in %d ms%n",
pjp.getSignature().getName(), duration);
}
}
}
上記の@Around以外にも、Spring AOPでは以下のアノテーションが頻繁に使用されます。
@Before:メソッド実行前にアドバイスを実行。@After:メソッドの成否に関わらず、実行後にアドバイスを実行。@AfterReturning:メソッドが正常に返却された後にアドバイスを実行。@AfterThrowing:メソッドが例外をスローした後にアドバイスを実行。
プログラムによるプロキシ生成と低レベルAPI
SpringのDIコンテナ外でAOPを適用したい場合や、プロキシ生成プロセスをより細かに制御したい場合は、ProxyFactory とSpring AOPの低レベルインターフェース(org.springframework.aop および org.aopalliance パッケージ)を直接使用します。このアプローチは、フレームワークの内部動作を理解するためにも有益です。
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.aop.framework.ProxyFactory;
import java.lang.reflect.Method;
/**
* 事前アドバイス:メソッド実行前に監査ログを出力
*/
class AuditBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) {
System.out.println("[Audit] Preparing to execute: " + method.getName());
}
}
/**
* 返戻後アドバイス:メソッド正常終了時にメトリクスを記録
*/
class MetricsAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) {
System.out.println("[Metrics] Successfully completed: " + method.getName());
}
}
/**
* 例外発生時アドバイス:例外発生時のフォールバック処理
*/
class FallbackThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
System.out.println("[Fallback] Exception caught in " + method.getName() + ": " + ex.getMessage());
}
}
/**
* 周囲アドバイス:認可チェックと実行制御
*/
class AuthorizationInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("[Auth] Checking permissions...");
// proceed()を呼び出さない場合、ターゲットメソッドは実行されない
Object result = invocation.proceed();
System.out.println("[Auth] Access granted and finalized.");
return result;
}
}
/**
* ターゲットインターフェース
*/
interface DataRepository {
String fetchData();
}
/**
* ターゲット実装クラス
*/
class UserDataRepository implements DataRepository {
@Override
public String fetchData() {
System.out.println("Fetching data from database...");
// デモンストレーションのために意図的に例外をスロー
throw new RuntimeException("Connection timeout");
}
}
/**
* プログラムによるAOP適用の実行クラス
*/
public class ProgrammaticAopDemo {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
// 各種アドバイスの登録
proxyFactory.addAdvice(new AuthorizationInterceptor());
proxyFactory.addAdvice(new AuditBeforeAdvice());
proxyFactory.addAdvice(new MetricsAfterAdvice());
proxyFactory.addAdvice(new FallbackThrowsAdvice());
// ターゲットオブジェクトの設定とCGLIBプロキシの強制使用
proxyFactory.setTarget(new UserDataRepository());
proxyFactory.setProxyTargetClass(true);
// プロキシオブジェクトの生成
DataRepository proxy = (DataRepository) proxyFactory.getProxy();
try {
proxy.fetchData();
} catch (Exception e) {
System.out.println("Handled exception at client level.");
}
}
}