Spring AOPって何? Spring AOPでメソッド前後に処理を挟んでみよう


Last updated on

Spring AOPは、ログを出す処理や権限チェックなど「どこでも共通でやりたい処理」を、業務ロジックとは別にまとめて書ける仕組みです。この記事では、Spring AOPの考え方を分かりやすく説明しながら、Spring Bootでの入れ方と基本的な書き方をサンプル付きで紹介します。

Spring AOPを一言でいうと

Spring AOPは、メソッドが動く前後に「追加の処理」を差し込める仕組みです。

例えばこんな処理は、どの機能にも必要になりがちです。

  • いつ、どのメソッドが呼ばれたかをログに出す
  • 実行にかかった時間を測る
  • 権限がある人だけ通す
  • 失敗したときに共通のエラー処理をする

こういった処理を、各メソッドに毎回書くとコードが散らかります。Spring AOPを使うと、共通処理を一箇所にまとめて、必要な場所にだけ適用できます。

まず覚える用語

最初は用語が多く見えますが、意味はシンプルです。

  • Aspect
    共通処理をまとめたクラスです。ログや計測などの「まとめ役」です。
  • Pointcut
    どこに適用するかの条件です。「このパッケージのこのメソッドだけ」みたいに絞ります。
  • Advice
    実際に差し込む処理そのものです。「前に動かす」「後に動かす」などの種類があります。

Spring AOPでは、基本的に「メソッドが呼ばれるタイミング」に処理を差し込むと覚えると理解しやすいです。

Spring Bootで使えるようにする

Spring Bootなら、依存関係を足すだけでだいたい準備完了です。

依存関係の追加

Gradleの場合です。

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-aop'
}

Mavenの場合です。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

実行時間を測ってログに出してみよう

ここでは「このメソッドは時間を測りたい」という場所にだけ、計測を付けてみます。

目印になるアノテーションを作る

このアノテーションを付けたメソッドだけを計測します。

package com.example.demo.aop;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timed {
}

アスペクトを書く

@Aspect を付けると「AOP用のクラスです」とSpringに伝えられます。@Around は「前後どちらにも処理を入れたい」ときによく使います。

package com.example.demo.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimingAspect {

  private static final Logger log = LoggerFactory.getLogger(TimingAspect.class);

  @Around("@annotation(com.example.demo.aop.Timed)")
  public Object measure(ProceedingJoinPoint pjp) throws Throwable {
    long start = System.nanoTime();

    try {
      // ここで本来のメソッドを実行します
      return pjp.proceed();
    } finally {
      long elapsedMs = (System.nanoTime() - start) / 1_000_000;
      log.info("method={} elapsedMs={}", pjp.getSignature().toShortString(), elapsedMs);
    }
  }
}

ポイントはここです。

  • @Around の中で pjp.proceed() を呼ぶと、本来のメソッドが実行される
  • proceed() の前に書けば「前の処理」、後に書けば「後の処理」になる

使いたいメソッドに付ける

package com.example.demo.service;

import com.example.demo.aop.Timed;
import org.springframework.stereotype.Service;

@Service
public class GreetingService {

  @Timed
  public String hello(String name) {
    return "Hello " + name;
  }
}

これで hello が呼ばれるたびに、実行時間がログに出ます。業務ロジック側に「計測のためのコード」を書かなくていいのが嬉しいところです。

差し込むタイミングの種類

よく使うものだけ覚えれば大丈夫です。

  • @Before
    メソッドの前に動く
  • @AfterReturning
    正常に終わった後に動く
  • @AfterThrowing
    例外で終わった後に動く
  • @After
    成功でも失敗でも最後に動く
  • @Around
    前後まとめて書ける

迷ったら @Around を選ぶことが多いです。前後の両方に書けて、戻り値もそのまま返せるからです。

例外のときだけログを出す

「失敗したときだけログを出したい」なら @AfterThrowing が読みやすいです。

@AfterThrowing(
  pointcut = "execution(* com.example.demo..*Service.*(..))",
  throwing = "ex"
)
public void logError(Exception ex) {
  log.warn("service error", ex);
}

どこに適用するかの書き方

大きく分けると2つです。

  • 文字のルールで場所を指定する
  • アノテーションで場所を指定する

文字のルールで指定する

例えば「Serviceで終わるクラスの全メソッド」を対象にしたいなら、次のように書けます。

@Around("execution(* com.example.demo..*Service.*(..))")
public Object aroundServices(ProceedingJoinPoint pjp) throws Throwable {
  return pjp.proceed();
}

ただし、範囲が広すぎると「思ってないところにも効く」ことがあります。最初は狭い範囲で試すのがおすすめです。

アノテーションで指定する

実務では「付けたところだけに効かせたい」ことが多いので、アノテーション指定はかなり使いやすいです。

@Around("@annotation(com.example.demo.aop.Timed)")
public Object timedOnly(ProceedingJoinPoint pjp) throws Throwable {
  return pjp.proceed();
}

よくあるつまずきどころ

Spring AOPは便利ですが、仕組みの都合で引っかかりやすい点があります。

同じクラスの中で呼ぶと効かないことがある

同じクラスの中で別メソッドを呼ぶとき、AOPが効かない場合があります。

public void a() {
  this.b(); // この呼び方だと、AOPが効かないことがある
}

対策としては、次のどちらかがよく使われます。

  • メソッドを別クラスに切り出して、Bean同士で呼ぶようにする
  • AOPに頼らない設計にする

「AOPは、Springが管理しているBeanの呼び出し経由で効く」と覚えておくと、原因が見つけやすいです。

privateメソッドに付けても期待通りにならない

AOPは基本的に「外から呼ばれるメソッド」に効かせるものだと考えると安全です。サービスの入口になる public メソッドに付けるのが無難です。

トランザクションも似た仕組み

@Transactional も、内部的には同じような考え方で動いています。例外を握りつぶしたり、途中で飲み込んだりすると、想定と違う結果になることがあります。

AOPで例外ログを出すときは「例外をそのまま投げ直すのか」を意識すると事故が減ります。

まとめ

Spring AOPは、ログや計測などの共通処理を、業務ロジックから切り離して書ける仕組みです。Spring Bootならスターターを追加するだけで始められます。

まずは、カスタムアノテーションを作って「付けたところだけに効かせる」形から試すと理解しやすいです。慣れてきたら、適用範囲を少しずつ広げていくのがおすすめです。