Spring AOP (アスペクト指向プログラミング) の概要
Spring Frameworkが提供するAOP(アスペクト指向プログラミング)機能は、ロギング、トランザクション管理、セキュリティといった横断的な関心事をモジュール化し、ビジネスロジックから分離するための強力なパラダイムです。これにより、コードの凝集度を高め、保守性を向上させることができます。Spring AOPは主にAspectJの概念に基づいて実装されており、その設定方法にはアノテーションベースとXMLベースの2つがあります。
アノテーションベースでのSpring AOP設定
1. 対象となるビジネスロジッククラスの定義
まず、AOPを適用したいビジネスロジックを含むクラスを定義します。ここでは、注文処理を行うサービスとして
OrderServiceを例にとります。
package com.example.app.service;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public String placeOrder(String item, int quantity) {
System.out.println("注文処理中: " + item + "を" + quantity + "個受け付けました。");
// 例外を発生させるケースをテストする場合
// if (quantity <= 0) {
// throw new IllegalArgumentException("数量は1以上である必要があります。");
// }
return "注文番号: " + System.currentTimeMillis();
}
}
2. アスペクトクラス(横断的関心事)の定義
次に、横断的関心事を実装するアスペクトクラスを定義します。このクラスには、様々な種類のアドバイス(通知)を含めることができます。
package com.example.app.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.core.annotation.Order; // アスペクトの実行順序を制御
@Component
@Aspect // このクラスがアスペクトであることを示す
@Order(10) // 複数のアスペクトがある場合の優先度。数値が小さいほど高優先度。
public class AuditLoggingAspect {
// 共通の切込点(Pointcut)定義: OrderServiceクラスのplaceOrderメソッド
@Pointcut("execution(* com.example.app.service.OrderService.placeOrder(..))")
public void orderServicePointcut() {}
// 前処理(Before Advice)
@Before("orderServicePointcut()")
public void logBeforeOrderPlacement() {
System.out.println("[監査ログ] --- @Before: 注文処理の開始をログに記録します。 ---");
}
// 後処理(After Returning Advice): 正常終了時のみ実行
@AfterReturning(pointcut = "orderServicePointcut()", returning = "result")
public void logAfterOrderSuccess(Object result) {
System.out.println("[監査ログ] --- @AfterReturning: 注文処理が正常に完了しました。結果: " + result + " ---");
}
// 最終処理(After Advice): 成功・失敗に関わらず常に実行
@After("orderServicePointcut()")
public void logAfterAnyOrderAttempt() {
System.out.println("[監査ログ] --- @After: 注文処理(成功・失敗問わず)が終了しました。 ---");
}
// 例外処理(After Throwing Advice): 例外発生時のみ実行
@AfterThrowing(pointcut = "orderServicePointcut()", throwing = "ex")
public void logAfterOrderFailure(Throwable ex) {
System.err.println("[監査ログ] --- @AfterThrowing: 注文処理中に例外が発生しました: " + ex.getClass().getSimpleName() + " - " + ex.getMessage() + " ---");
}
// 周囲処理(Around Advice): メソッドの実行前後を完全に制御
@Around("orderServicePointcut()")
public Object monitorOrderProcessing(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("[監査ログ] --- @Around: 注文処理メソッド実行前 (@Around開始)。対象: " + joinPoint.getSignature().getName() + " ---");
Object result = null;
try {
result = joinPoint.proceed(); // 対象のメソッドを実行
System.out.println("[監査ログ] --- @Around: 注文処理メソッドが正常に完了しました (@Around内部)。 ---");
} catch (Exception e) {
System.err.println("[監査ログ] --- @Around: 注文処理メソッドで例外発生 (@Around内部): " + e.getMessage() + " ---");
throw e; // 例外を再スロー
} finally {
System.out.println("[監査ログ] --- @Around: 注文処理メソッド実行後 (@Around終了)。 ---");
}
return result;
}
}
3. Spring設定(XMLを使用する場合)
SpringコンテナがアノテーションベースのAOPを認識し、プロキシを自動生成するために、XML設定ファイルで以下の要素を有効にします。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 指定されたパッケージ内の@Component、@Serviceなどのアノテーションを持つクラスをSpring Beanとしてスキャン -->
<context:component-scan base-package="com.example.app"/>
<!-- AspectJのアノテーション駆動型AOPを有効にし、対象Beanのプロキシを自動生成 -->
<aop:aspectj-autoproxy/>
</beans>
4. Spring設定(Java Configを使用する場合)
XML設定ファイルを使わず、完全にJavaコードでSpringの設定を行うことも可能です。
package com.example.app.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration // このクラスがSpringの設定クラスであることを示す
@ComponentScan(basePackages = "com.example.app") // Beanをスキャンするパッケージを指定
@EnableAspectJAutoProxy(proxyTargetClass = true) // AspectJのアノテーション駆動型AOPを有効化
// proxyTargetClass=trueでCGLIBプロキシを強制 (デフォルトはJDK動的プロキシ)
public class AppConfig {
// 他のBean定義や設定メソッドをここに追加できます
}
XMLベースでのSpring AOP設定
アノテーションを使用せず、すべてのAOP設定をXMLファイルで定義することもできます。
1. 対象となるビジネスロジッククラスとアスペクトクラスの準備
XML設定では、クラス自体にAOP関連のアノテーションは不要です。ここでは、支払い処理を行う
PaymentServiceと監査ログを記録する
PaymentAuditAspectを例にします。
package com.example.app.service;
public class PaymentService {
public void processPayment(double amount) {
System.out.println("支払い処理実行: " + amount + "円の支払いを処理しています。");
}
}
package com.example.app.aspect;
public class PaymentAuditAspect {
public void logPaymentAttempt() {
System.out.println("[XML監査ログ] --- 支払い処理が開始されようとしています。 ---");
}
public void logPaymentCompletion() {
System.out.println("[XML監査ログ] --- 支払い処理が正常に完了しました。 ---");
}
}
2. Spring設定ファイルでのAOP定義
XMLファイル内でBean定義とAOPの切込点、切面(アスペクト)、アドバイスをすべて設定します。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 対象となるサービスとアスペクトのBean定義 -->
<bean id="paymentService" class="com.example.app.service.PaymentService"/>
<bean id="paymentAuditAspect" class="com.example.app.aspect.PaymentAuditAspect"/>
<!-- AOP設定の開始 -->
<aop:config>
<!-- 切込点の定義 -->
<aop:pointcut id="paymentPointcut" expression="execution(* com.example.app.service.PaymentService.processPayment(..))"/>
<!-- 切面(アスペクト)の定義。ref属性でアスペクトBeanを参照 -->
<aop:aspect ref="paymentAuditAspect">
<!-- 前処理(Before Advice)を適用 -->
<aop:before method="logPaymentAttempt" pointcut-ref="paymentPointcut"/>
<!-- 後処理(After Returning Advice)を適用 -->
<aop:after-returning method="logPaymentCompletion" pointcut-ref="paymentPointcut"/>
<!-- 他のアドバイスタイプも同様に設定可能 -->
</aop:aspect>
</aop:config>
</beans>