📚

SpringにおけるAdviceとは?【横断処理】

に公開

0. はじめに

この記事は、駆け出しバックエンドエンジニアによる学習記録です。
初学で調べたことをまとめたものなので、多少間違った説明もあるかもしれません。ご了承くださいmm

1. Adviceとは?

Advice(アドバイス)は「横断的な処理を所定のタイミングで差し込む仕組み」のこと。
Springでは文脈が2つある:

1) AOPの"Advice"

そもそもAOPとは?

Aspect Oriented Programming:アスペクト指向プログラミング

目的

通常のOOP(オブジェクト指向プログラミング)では「関心事(concerns)」をクラスやメソッドごとに分けるが、アプリ全体で横断的に必要になる処理(logging、トランザクション管理、セキュリティチェックなど)は、各クラスにバラバラに書くと重複・保守性の低下が発生する。
そこで登場するのがAOP

考え方

  • OOP → 業務ロジックを「縦」に分割する(ユーザー処理、注文処理、支払い処理など)
  • AOP → 共通処理を「横」に横断的に注入する(ログ、エラーハンドリング、認可チェックなど)

これにより 関心事の分離(Separation of Concerns, SoC)が達成される。

SpringにおけるAOPの主要な概念

  1. Aspect(アスペクト)
    • 横断的関心事をまとめたモジュール(例:ログ出力用のクラス)
  2. Join Point(ジョインポイント)
    • 横断的処理を差し込める「場所」
    • Spring AOPでは基本的に「メソッド実行時」が対象
  3. Pointcut(ポイントカット)
    • 「どのJoin Pointに適用するか」を条件で指定するもの
    • 例: execution(* com.example.service..(..)) → serviceパッケージ内の全メソッド
  4. Advice(アドバイス)
    • 「実際に差し込む処理」のこと
    • 例: ログを出力する処理、トランザクションを開始する処理

Adviceの種類

Spring AOPでは、Adviceは以下のように「いつ実行するか」で分類される。

  • Before Advice
    • メソッド実行前に動く処理
    • 例: 引数のバリデーション、認可チェック
  • After Returning Advice
    • メソッド実行後、正常に返ったときに動く処理
    • 例: 戻り値をログに残す
  • After Throwing Advice
    • メソッドが例外を投げたときに動く処理
    • 例: エラー監視システムに通知
  • After (Finally) Advice
    • 成功・失敗に関わらずメソッド終了後に必ず動く処理
    • 例: リソース解放、セッション終了
  • Around Advice
    • メソッド呼び出しの前後をラップして処理する(最も強力)]
    • 例: 実行時間の計測、トランザクション開始〜終了

Springでは@Aspect + @Before, @After, @Aroundなどのアノテーションで記述する。

具体例

サービスメソッドの実行時間を測りたい

AOP + Adviceを使って、どのメソッドでも共通の処理として注入できる。

サービス(業務ロジック)
@Service
public class UserService {
    public void createUser(String name) {
        System.out.println("ユーザー " + name + " を作成しました。");
    }

    public String getUser(Long id) {
        return "ユーザー#" + id;
    }
}

ここには業務処理だけ書いていて、計測やログは書いていない。

アスペクト(横断的処理 = Advice の定義)
@Aspect
@Component
public class LoggingAspect {

    // 対象:com.example.serviceパッケージ内の全メソッド
    @Around("execution(* com.example.service.*.*(..))")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();

        try {
            // ★ 本来のメソッドを実行
            Object result = joinPoint.proceed();
            return result;
        } finally {
            long end = System.currentTimeMillis();
            System.out.println(joinPoint.getSignature() + " 実行時間: " + (end - start) + "ms");
        }
    }
}
実行結果イメージ
ユーザー Taro を作成しました。
void com.example.service.UserService.createUser(..) 実行時間: 3ms
ポイント解説
  • @Aspect: このクラスが アスペクト(横断処理の集合) であることを示す
  • @Around: 「対象メソッドの前後をラップするAdvice」であることを示す
    • その他のタイミング
      • @Before → メソッド前
      • @AfterReturning → 戻り値後
      • @AfterThrowing → 例外時
      • @After → 終了時(finally相当)
  • "execution(* com.example.service.*.*(..))": ポイントカット(対象メソッドの条件)
  • ProceedingJoinPoint.proceed(): 本来のメソッドを実行

まとめると・・・

  • AOP = 共通処理を横断的に注入する仕組み
  • Advice = 実際に注入される処理本体

2) Spring MVCの"Controller Advice"

Controller Adviceとは?

Spring MVCにおける@ControllerAdviceは、
「複数のControllerにまたがる共通処理をひとまとめにして適用できる仕組み」のこと。
特に以下の用途でよく使われます:

  1. 例外処理の一元化
    • 各Controllerでtry-catchする代わりに、まとめてハンドリングできる
  2. 共通のデータをModelに追加
    • 全ての画面で使う共通データ(例えばログインユーザー情報)をModelに自動的に注入
  3. 共通のバインディング設定
    • 日付フォーマットやバリデーションルールを全体に適用

使い方の具体例

1. 例外処理をまとめる

@ControllerAdvice
public class GlobalExceptionHandler {

    // 特定の例外をキャッチしてレスポンスを返す
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException ex) {
        return ResponseEntity
                .badRequest()
                .body("不正なリクエスト: " + ex.getMessage());
    }

    // 予期しない全ての例外をキャッチ
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception ex) {
        return ResponseEntity
                .internalServerError()
                .body("サーバーエラーが発生しました");
    }
}

これで、どのControllerで例外が発生しても共通の処理でハンドリングされる。

2. 共通データをModelに追加する

@ControllerAdvice
public class GlobalModelAttribute {

    // すべてのControllerに共通でmodelへ追加される
    @ModelAttribute("appName")
    public String appName() {
        return "Mini SNS アプリ";
    }
}

全てのビューで${appName}を使えるようになる。

3. バインディング設定を共通化

@ControllerAdvice
public class GlobalBindingInitializer {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        // 入力フォームの日付文字列を "yyyy-MM-dd" でパースする設定
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }
}

どのControllerでも、@RequestParam Dateを統一フォーマットで受け取れるようになる。

まとめると・・・

  • @ControllerAdviceは Controller全体の横断処理を担当
  • 主な用途は 例外処理・共通データ・共通バインディング
  • 例外処理をまとめることでコードがスッキリし、保守性も向上する

2. AOP AdviceとController Adviceの違い

  • AOPのAdvice
    • サービス層やリポジトリ層などに横断的処理を挟み込む
    • 例: 実行時間測定、トランザクション管理
    • 主なアノテーション:
      • @Aspect
      • @Around
      • @Before
      • @AfterReturning
      • @AfterThrowing
      • @After
  • Controller Advice
    • Controllerに特化した共通処理を提供する
    • 例: 共通の例外処理、共通データの注入、リクエスト/レスポンスの変換
    • 主なアノテーション:
      • @ControllerAdvice

3. 感想

今回、ミニSNSアプリの開発に取り組む中でAdviceの概念を知る必要が出てきたので学習してみた。
まあざっくりは分かった気がする。今後もっと手を動かして使っていくことで理解を深めていこう。

Discussion