1.

Spring AOP @After アノテーション|メソッド実行後の処理を挟む使い方

編集
この記事の要点
  • @After は Spring AOP で対象メソッドの実行後に処理を挟むアノテーション
  • @Aspect クラス内のメソッドに付与する
  • 例外発生時も実行される(finally 的な挙動)
  • 成功時のみ: @AfterReturning / 例外時のみ: @AfterThrowing を使い分け
  • 前処理: @Before / 前後両方: @Around

@After とは

Spring AOP の @After アノテーションは、対象メソッドの実行が終了した後に共通処理を挟みます。ログ出力・リソース解放・統計収集など、横断的関心事をビジネスロジックから分離できます。

基本構文

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        log.info("[AFTER] {} が完了", methodName);
    }
}

これだけで com.example.service パッケージ配下のすべての public メソッドの実行後に logAfter が呼ばれます。

@Before / @After / @AfterReturning / @AfterThrowing / @Around の違い

アノテーション実行タイミング例外時戻り値取得
@Beforeメソッド実行前例外有無無関係に実行×(実行前なので戻り値なし)
@Afterメソッド実行後(finally 的)○ 例外時も実行×
@AfterReturning正常終了後のみ× 例外時はスキップreturning 属性で取得
@AfterThrowing例外発生時のみ○(その例外時のみ)例外オブジェクト取得
@Around前後両方(最も柔軟)制御可能○ 自分で proceed() を呼ぶ

各アノテーションの実装例

@Aspect
@Component
public class CompleteAspect {

    // 前処理: メソッド実行前
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice(JoinPoint jp) {
        log.info("[BEFORE] {} を実行", jp.getSignature().getName());
    }

    // 後処理 (finally 的): 必ず実行
    @After("execution(* com.example.service.*.*(..))")
    public void afterAdvice(JoinPoint jp) {
        log.info("[AFTER] {} 完了 (正常 or 例外)", jp.getSignature().getName());
    }

    // 正常終了時のみ
    @AfterReturning(
        pointcut = "execution(* com.example.service.*.*(..))",
        returning = "result"
    )
    public void afterReturning(JoinPoint jp, Object result) {
        log.info("[AFTER_RETURNING] {} 正常終了, 戻り値: {}",
            jp.getSignature().getName(), result);
    }

    // 例外発生時のみ
    @AfterThrowing(
        pointcut = "execution(* com.example.service.*.*(..))",
        throwing = "ex"
    )
    public void afterThrowing(JoinPoint jp, Exception ex) {
        log.error("[AFTER_THROWING] {} で例外: {}",
            jp.getSignature().getName(), ex.getMessage());
    }

    // 前後両方を制御
    @Around("execution(* com.example.service.*.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            Object result = pjp.proceed();  // 元メソッドを実行
            long elapsed = System.currentTimeMillis() - start;
            log.info("[AROUND] {} 完了 ({}ms)", pjp.getSignature().getName(), elapsed);
            return result;
        } catch (Throwable t) {
            log.error("[AROUND] {} 失敗", pjp.getSignature().getName());
            throw t;
        }
    }
}

実行順序

1 メソッドに複数の Advice がマッチする場合の順序:

@Before
  ↓
@Around (前半)
  ↓
[ 対象メソッド実行 ]
  ↓
@Around (後半)
  ↓
@After
  ↓
@AfterReturning (正常時) または @AfterThrowing (例外時)

ポイントカット式の書き方

意味
execution(* com.example.service.*.*(..))service パッケージの全クラスの全 public メソッド
execution(* *Service.find*(..))Service で終わるクラスの find で始まるメソッド
@annotation(org.springframework.transaction.annotation.Transactional)@Transactional 付きメソッド
within(com.example.service.*)service パッケージ内のメソッド呼び出しすべて
@within(org.springframework.stereotype.Service)@Service クラスの全メソッド
this(com.example.MyInterface)MyInterface 実装クラス
args(java.lang.String, ..)第 1 引数が String のメソッド

カスタムアノテーション + AOP

独自アノテーションを作って AOP のターゲット指定に使うパターン:

// 1. カスタムアノテーション定義
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
    String value() default "";
}

// 2. AOP で受ける
@Aspect
@Component
public class LogExecutionTimeAspect {

    @Around("@annotation(LogExecutionTime)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return pjp.proceed();
        } finally {
            long elapsed = System.currentTimeMillis() - start;
            log.info("{} took {}ms", pjp.getSignature().getName(), elapsed);
        }
    }
}

// 3. 使い方
@Service
public class UserService {
    @LogExecutionTime
    public List findAll() {
        // ... 処理(自動的に時間が計測される)
    }
}

JoinPoint の主要メソッド

@After("execution(* com.example..*(..))")
public void after(JoinPoint jp) {
    // メソッド名
    String name = jp.getSignature().getName();

    // クラス名
    String className = jp.getTarget().getClass().getName();

    // メソッド引数
    Object[] args = jp.getArgs();

    // シグネチャ
    Signature sig = jp.getSignature();

    // 例: "void com.example.UserService.save(User)"
    log.info("Called {} with args {}", sig, Arrays.toString(args));
}

有効化: @EnableAspectJAutoProxy

Spring Boot では自動で有効化されますが、Spring Framework(非 Boot)では明示が必要:

@Configuration
@EnableAspectJAutoProxy   // ★ AOP 有効化
@ComponentScan("com.example")
public class AppConfig {
}

注意点

  • 同一クラス内のメソッド呼び出しでは AOP が効かない: this.someMethod() はプロキシを経由しないため
  • private / final メソッドは AOP 対象外
  • パフォーマンスへの影響: プロキシ経由になるので極端なホットパスは慎重に
  • 順序制御: 複数の Aspect 間の順序は @Order(数値) で制御(小さい数値が先)
  • ログ出力ループ: AOP の中で対象クラスのロガーを呼ぶと無限ループのリスク

典型的な用途

  • ログ・監査: メソッド呼び出しの自動記録
  • パフォーマンス計測: 実行時間の収集
  • トランザクション境界: @Transactional 自体が AOP 実装
  • キャッシュ: @Cacheable も AOP ベース
  • セキュリティ: @PreAuthorize 認可チェック
  • リトライ: Spring Retry の @Retryable
  • Circuit Breaker: Resilience4j のアノテーション
編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. @After
  2. @Autowired
  3. @Bean
  4. @Before
  5. @Column
  6. @Component
  7. @Configuration
  8. @Controller
  9. @Data
  10. @Entity
  11. @GeneratedValue
  12. @Id
  13. @Modifying
  14. @PathVariable
  15. @PropertySource
  16. @Repository
  17. @RequestBody
  18. @RequestMapping
  19. @ResponseBody
  20. @RestController
  21. @Service
  22. @SpringBootApplication
  23. @Table
  24. @Transactional
  25. @Value