---
title: "AOP（アスペクト指向プログラミング）とは？Spring AOPの仕組みと使い方・自己呼び出しの落とし穴"
url: "https://www.issoh.co.jp/tech/details/3422/"
published: 2024-08-29
updated: 2026-09-26
categories: ["アーキテクチャ"]
publisher: "株式会社一創"
---

# AOP（アスペクト指向プログラミング）とは？Spring AOPの仕組みと使い方・自己呼び出しの落とし穴

AOP（Aspect-Oriented Programming、アスペクト指向プログラミング）は、ログ出力・トランザクション・権限チェックのように多くのクラスへ散らばる処理を「アスペクト」として1か所に切り出し、本来の業務ロジックから分離する手法です。Javaでは[Spring Framework](/tech/details/3867/)のSpring AOPとAspectJが代表的な実装で、Springの`@Transactional`もこの仕組みの上で動いています。

以下、プロキシの仕組み、Spring Boot 4での依存関係、アドバイスの実行順、「アドバイスが効かない」原因を、Spring Framework 7.0.9とAspectJ 1.9.25.1での実行結果とともに説明します。なお、経営用語のAOP（Annual Operating Plan＝年度事業計画・予算）を調べている場合は[予算管理とは？目的と予実管理との違い・編成から差異分析までの進め方を解説](/column/details/17586/)が該当します。フランスの原産地呼称（チーズ・バターのAOP）も別の言葉です。

## まとめ：AOPの要点とSpringで使うときの判断

- AOPは、複数のクラスにまたがる「横断的関心事」をアスペクトに集め、業務ロジックから切り離すプログラミング手法です。Xerox PARCのKiczalesらが1997年のECOOPで発表した論文が起点です。
- Spring AOPはプロキシ方式で、ジョインポイントは**Beanのメソッド実行だけ**です。フィールドアクセスやコンストラクタまで対象にしたいときはAspectJのウィービングを使います。
- Spring Boot 4では依存が`spring-boot-starter-aop`から`spring-boot-starter-aspectj`へ改名されました。要否は、自作の`@Aspect`やMicrometerの`@Timed`のようにAspectJのアノテーションに依存する機能を使うかどうかで決まります。
- 同じアスペクト内のアドバイスは、@Around→@Before→本処理→@AfterReturning／@AfterThrowing→@After→@Aroundの後半、の順に動きます。
- 同じクラスの中から呼んだメソッド（自己呼び出し）にはアドバイスが効きません。処理を別のBeanに分けるのが基本の対処です。
- 向いているのは、ログ・計測・トランザクション・認可のように多くのクラスへ同じ形で現れ、アノテーションやポイントカットで適用箇所を示せる横断的処理です。個別の業務ルールをアスペクトに隠すと、コードを読んでも挙動が追えなくなります。

## AOPの定義と横断的関心事の分離

### 1997年のKiczalesらの論文が示した「機能分割を横切る」アスペクト

AOPという言葉は、Xerox PARCのGregor Kiczalesらが1997年のECOOP（欧州オブジェクト指向プログラミング会議）で発表した論文「[Aspect-Oriented Programming](https://www.cs.ubc.ca/~gregor/papers/kiczales-ECOOP1997-AOP.pdf)」（Springer LNCS 1241）で提示されました。論文は、メモリアクセスのパターン、並行オブジェクトの同期、エラーや障害の処理のようにシステムの基本機能を横切る（cross-cut）性質を「アスペクト」と呼び、手続き型やオブジェクト指向の手法ではその実装がコード全体に散らばって絡み合う（tangled）と指摘しています。AOPはこれを別の言語で記述し、アスペクトウィーバーがコンパイル時または実行時に本体のコードへ織り込む（ウィービングする）考え方です。今日「横断的関心事（crosscutting concerns）」と呼ばれるのが、このアスペクトの対象です。

このXerox PARCでの研究を基に生まれたJava向けの言語拡張がAspectJで、現在はEclipseのオープンソースプロジェクト（eclipse.org/aspectj）として開発が続いています。Spring AOPはAspectJのアノテーション（`@Aspect`、`@Before`など）とポイントカット式を借りつつ、織り込みはSpring独自のプロキシで行います。

### オブジェクト指向との違い：クラス分割では残る共通処理の分離

AOPはオブジェクト指向（OOP）の代わりではなく、補う位置づけです。OOPは「注文」「在庫」のような業務の単位でクラスを分けます。ところが「全サービスのメソッドの処理時間を記録する」処理は、どのクラスにも少しずつ必要になるため、クラスをどう分けても各メソッドに同じ数行が残ります。ユーティリティに移しても、呼び出し行は各メソッドに残ります。

AOPでは、どのメソッドにどのタイミングで処理を挟むかをアスペクト側に宣言するので、その呼び出し行も消えます。ログの形式を変えるときも、修正はアスペクト1ファイルで済みます。

## AOPの主要用語とSpringでの書き方

AOPの説明で必ず出てくる5つの用語を、Spring AOPで実際に書く形と並べます。

| 用語                   | 意味              | Spring AOPでの書き方               |
| -------------------- | --------------- | ----------------------------- |
| アスペクト（Aspect）        | 横断的処理をまとめた単位    | `@Aspect`を付けたBeanクラス          |
| ジョインポイント（Join Point） | 処理を挟める実行地点      | Beanのメソッド実行のみ                 |
| ポイントカット（Pointcut）    | ジョインポイントの選択条件   | `@Pointcut("execution(...)")` |
| アドバイス（Advice）        | 挟み込む処理とタイミング    | `@Before`・`@Around`など5種       |
| ウィービング（Weaving）      | アスペクトを本体に織り込むこと | 実行時にプロキシを生成                   |

このほか、アドバイスを適用される元のオブジェクトを「ターゲット」、Springが生成する代理オブジェクトを「AOPプロキシ」と呼びます。ポイントカット式でよく使うのは、メソッドのシグネチャで絞る`execution(public * com.example.service.*.*(..))`、パッケージやクラスで絞る`within(com.example.service..*)`、アノテーションの有無で絞る`@annotation(com.example.Audited)`の3つです。

## Spring AOPがプロキシで動く仕組み

### JDK動的プロキシとCGLIBの選ばれ方

Spring AOPは、アスペクトの対象になったBeanの代わりにプロキシをDIコンテナへ登録し、プロキシがアドバイスを実行してから本物のメソッドへ処理を渡します。[IoCコンテナ](/tech/details/3210/)がBeanを組み立てる段階で差し替えるため、業務クラスにもその利用側にも変更は要りません。

プロキシの作り方は2種類あります。インターフェースを実装したBeanにはJDK標準の動的プロキシ（インターフェースを実装した別クラス）、インターフェースが無いBeanにはCGLIBによるサブクラスが使われる、というのがSpring Framework単体の既定です。Spring Bootは`spring.aop.proxy-target-class`の既定値を`true`にしており、インターフェースの有無にかかわらずCGLIBのサブクラスを作ります。

この差は、Beanを実装クラスの型で取り出すときに表面化します。Spring Framework単体・`proxyTargetClass = false`の設定で、インターフェース`Greeter`を実装した`GreeterImpl`に`@Before`を当てて実行した結果が次です。

```
package demo;

import org.aspectj.lang.annotation.*;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.*;

interface Greeter { String hello(); }

@Service
class GreeterImpl implements Greeter { public String hello() { return "hi"; } }

@Aspect
@Component
class A { @Before("execution(* demo.Greeter.*(..))") public void b() { System.out.println("Before"); } }

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ComponentScan("demo")
public class All {
    public static void main(String[] args) {
        try (var ctx = new AnnotationConfigApplicationContext(All.class)) {
            Greeter g = ctx.getBean(Greeter.class);
            System.out.println(g.getClass().getSimpleName() + " JDK=" + AopUtils.isJdkDynamicProxy(g) + " -> " + g.hello());
            try { ctx.getBean(GreeterImpl.class); } catch (Exception e) { System.out.println(e.getClass().getSimpleName()); }
        }
    }
}
```

実行結果：

```
Before
$Proxy17 JDK=true -> hi
NoSuchBeanDefinitionException
```

プロキシのクラス名は`$Proxy17`で、`Greeter`型では取得できますが、`GreeterImpl`型で取得しようとすると`NoSuchBeanDefinitionException`になります。JDK動的プロキシは`GreeterImpl`のサブクラスではないためです。Spring Bootの既定（CGLIB）ならこの食い違いは起きません。Spring Framework 7.0からは、Beanごとに`@Proxyable(INTERFACES)`・`@Proxyable(TARGET_CLASS)`を付けて全体の既定を上書きすることもできます。

### Spring AOPとAspectJの違い：対象範囲とウィービング方式

AspectJはプロキシを使わず、クラスのバイトコード自体にアスペクトを織り込みます。織り込むタイミングは、ソースのコンパイル時（compile-time weaving）、コンパイル済みのclassやjarに対して行うpost-compile（binary）weaving、クラスローダーが読み込む瞬間に行うload-time weaving（LTW）の3通りです。

| 項目       | Spring AOP      | AspectJ                 |
| -------- | --------------- | ----------------------- |
| 織り込み方    | 実行時にプロキシ生成      | バイトコードを書き換え             |
| ジョインポイント | Beanのメソッド実行のみ   | メソッド呼び出し・フィールド・コンストラクタ等 |
| 対象       | Springが管理するBean | 任意のクラス                  |
| 自己呼び出し   | アドバイスが効かない      | 効く                      |
| 追加のビルド設定 | 不要              | ajcコンパイラかjavaagentが必要   |

Spring公式ドキュメントも、Beanのメソッド実行に処理を挟むだけならSpring AOPで足り、Springの管理外のオブジェクトやフィールドアクセスを対象にするときはAspectJが必要としています。まずSpring AOPを選び、その制約にぶつかってからAspectJを検討する順番で困りません。

## Spring AOPの実装手順：Boot 4の依存設定とアスペクトのコード

### 依存関係：Spring Boot 4はspring-boot-starter-aspectj

Spring Boot 4.0で、AOP用のスターターは`spring-boot-starter-aop`から`spring-boot-starter-aspectj`へ改名されました。Maven Centralで確認すると、旧名の`spring-boot-starter-aop`は3.x系が3.5.16、4.0系はマイルストーンの4.0.0-M2を最後に公開が止まっており、新名の`spring-boot-starter-aspectj`は2026年9月26日時点の安定版で4.1.1が最新です（4.1.1の中身は`spring-aop` 7.0.9と`aspectjweaver` 1.9.25.1）。Spring Boot 4へ上げるときは次のように書き換えます。

```
<!-- Spring Boot 3.x まで -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- Spring Boot 4.0 以降 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aspectj</artifactId>
</dependency>

// Gradle の場合
implementation 'org.springframework.boot:spring-boot-starter-aspectj'
```

[Spring Boot 4.0の移行ガイド](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide)は、改名に合わせて「本当にこのスターターが必要か見直す」ことも求めており、その判断基準はアプリケーションがAspectJ（`org.aspectj.lang.annotation`パッケージのアノテーション）を使っているかどうかです。自分で`@Aspect`クラスを書く場合に加え、Micrometerの`@Timed`・`@Counted`のようにAspectJのアノテーションで動く機能を使う場合も必要です。`@Transactional`（spring-tx）や`@Cacheable`（spring-context）は、既定のプロキシ方式ならSpring自身のプロキシ機構（spring-contextが依存する`spring-aop`）で動き、AspectJのjarを使いません。これらのためにこのスターターを足す必要はなく、必要なのは各機能のスターター（データアクセスやキャッシュ）と設定です。バージョンの対応は[Spring Bootバージョン一覧](/tech/details/5172/)、Spring Boot 4で名前が変わった他の依存は[Spring Bootとは｜4.1系の依存名の変更と最小Web APIの作り方](/tech/details/17478/)にまとめています。

Spring Bootでは、`aspectjweaver`がクラスパスにあれば自動設定（`AopAutoConfiguration`）が`@EnableAspectJAutoProxy`相当を有効にするので、設定クラスに自分で`@EnableAspectJAutoProxy`を書く必要はありません。無効にしたいときは`spring.aop.auto=false`を指定します。

### @Aspectとポイントカットで処理時間を記録するコード

注文サービスの全publicメソッドに、処理時間の記録と5種類のアドバイスを当てる例です。以下のコードはSpring Framework 7.0.9・AspectJ 1.9.25.1・JDK 25で実行し、出力を確認しています（Spring Bootを使わない構成なので、設定クラスに`@EnableAspectJAutoProxy`を書いています）。

```
package demo;

import org.springframework.stereotype.Service;

@Service
public class OrderService {
    public String placeOrder(String item) {
        System.out.println("  [本処理] placeOrder(" + item + ")");
        validate(item); // 自己呼び出し：プロキシを経由しない
        return "OK:" + item;
    }

    public void validate(String item) {
        System.out.println("  [本処理] validate(" + item + ")");
        if (item.isEmpty()) throw new IllegalArgumentException("item is empty");
    }
}
```

```
package demo;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {
    @Pointcut("execution(public * demo.OrderService.*(..))")
    void serviceMethods() {}

    @Around("serviceMethods()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();
        System.out.println("Around(前) " + pjp.getSignature().getName());
        try {
            return pjp.proceed();
        } finally {
            System.out.printf("Around(後) %s %dus%n", pjp.getSignature().getName(), (System.nanoTime() - start) / 1000);
        }
    }

    @Before("serviceMethods()")
    public void before(JoinPoint jp) { System.out.println("Before " + jp.getSignature().getName()); }

    @After("serviceMethods()")
    public void after(JoinPoint jp) { System.out.println("After " + jp.getSignature().getName()); }

    @AfterReturning(pointcut = "serviceMethods()", returning = "ret")
    public void afterReturning(JoinPoint jp, Object ret) { System.out.println("AfterReturning " + ret); }

    @AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
    public void afterThrowing(JoinPoint jp, Exception ex) { System.out.println("AfterThrowing " + ex.getMessage()); }
}
```

```
package demo;

import org.springframework.aop.support.AopUtils;
import org.springframework.context.annotation.*;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("demo")
public class App {
    public static void main(String[] args) {
        try (var ctx = new AnnotationConfigApplicationContext(App.class)) {
            OrderService svc = ctx.getBean(OrderService.class);
            System.out.println("proxy class = " + svc.getClass().getSimpleName()
                    + " / CGLIB=" + AopUtils.isCglibProxy(svc));
            System.out.println("--- 1) 外から placeOrder");
            svc.placeOrder("book");
            System.out.println("--- 2) 外から validate(\"\")");
            try { svc.validate(""); } catch (IllegalArgumentException e) { System.out.println("caught: " + e.getMessage()); }
        }
    }
}
```

`@Pointcut`で条件に名前を付けておくと、各アドバイスは`serviceMethods()`と書くだけで同じ条件を共有できます。`@Around`だけは引数が`ProceedingJoinPoint`で、`proceed()`を呼ばない限り本処理が実行されません。戻り値を差し替えたり、例外を握りつぶしたりできるのも`@Around`だけです。

### アドバイス5種の実行順と実行結果

上のコードを実行した出力が次です（処理時間の数値は実行環境で変わります）。

```
proxy class = OrderService$$SpringCGLIB$$0 / CGLIB=true
--- 1) 外から placeOrder
Around(前) placeOrder
Before placeOrder
  [本処理] placeOrder(book)
  [本処理] validate(book)
AfterReturning OK:book
After placeOrder
Around(後) placeOrder 8074us
--- 2) 外から validate("")
Around(前) validate
Before validate
  [本処理] validate()
AfterThrowing item is empty
After validate
Around(後) validate 11110us
caught: item is empty
```

同じアスペクトに書いたアドバイスは、@Around→@Before→本処理→@AfterReturning（例外時は@AfterThrowing）→@After→@Aroundの後半、の順に実行されました。[Spring Framework 7.0のリファレンス](https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/advice.html#aop-ataspectj-advice-ordering)は、同一アスペクト内の優先順位を高い順に@Around、@Before、@After、@AfterReturning、@AfterThrowingと定めています。そのうえで@AfterはAspectJの「after finally」の意味論に従い、@AfterReturningや@AfterThrowingより後に実行されると注記しており、出力はその記述どおりです。同じ種類のアドバイス（たとえば@Afterを2つ）を同じアスペクトに書くと順序は未定義なので、1つにまとめるかアスペクトを分けます。別々のアスペクト同士の順序も指定しなければ未定義で、順序が意味を持つ場合はアスペクトクラスに`@Order`を付け、値の小さいほうを優先度の高い外側にします。`@Order`をはじめとするSpringのアノテーションの一覧は[Spring Bootの主要なアノテーション一覧](/tech/details/4048/)にあります。

5種の使い分けは次のとおりです。

| アドバイス             | 実行タイミング    | 向いている処理        |
| ----------------- | ---------- | -------------- |
| `@Before`         | 本処理の前      | 引数のログ・権限チェック   |
| `@AfterReturning` | 正常終了後      | 戻り値のログ・監査記録    |
| `@AfterThrowing`  | 例外発生後      | エラーログ・通知       |
| `@After`          | 正常・例外どちらでも | 後始末（finally相当） |
| `@Around`         | 前後を丸ごと包む   | 処理時間の計測・リトライ   |

公式ドキュメントは、要件を満たす中で最も弱いアドバイスを選ぶよう勧めています。ログを出すだけなら`@Before`で足り、`@Around`で`proceed()`を書き忘れて本処理が消える事故を避けられます。

## アドバイスが効かない原因：自己呼び出しとprivateメソッド

先の実行結果をもう一度見てください。1回目の呼び出しで`placeOrder()`は内部で`validate()`を呼んでいますが、`validate()`にはアドバイスが1つも実行されていません。2回目に外から直接`validate()`を呼んだときは、@Around・@Before・@AfterThrowing・@Afterの4種類が動いています（例外で終わったため、正常終了時の@AfterReturningは実行されません）。

これがSpring AOPの自己呼び出し（self-invocation）の制約です。アドバイスを実行するのはプロキシなので、プロキシを経由しない呼び出しには何も起きません。`placeOrder()`の中の`validate(item)`は`this.validate(item)`と同じで、`this`はプロキシではなく本物のオブジェクトを指します。同じ理由で、次のメソッドにもアドバイスは効きません。

- CGLIBプロキシ（Spring Bootの既定）では、`private`メソッド・`final`メソッド・`final`クラス（サブクラスで上書きできない）
- JDK動的プロキシでは、インターフェースに宣言されていないメソッド（`final`であってもインターフェース経由の呼び出しには効く）
- `new`で自分で生成したオブジェクト（Beanではないのでプロキシが無い）

この制約は自作のアスペクトだけでなく、同じプロキシの仕組みで動く`@Transactional`・`@Cacheable`・`@Async`にもそのまま当てはまります。同じクラスの別メソッドに付けた`@Transactional`が効かないときは、まずこの自己呼び出しを疑います。可視性については、Spring Framework 6.0以降、クラスベースのプロキシなら`protected`やパッケージプライベートのメソッドにも`@Transactional`が効くようになりました。ただしインターフェースベースのプロキシではpublicかつインターフェースに宣言したメソッドに限られ、どちらの方式でもプロキシを通る外部からの呼び出ししか捕捉されない点は変わりません。

対処は、処理を別のBeanに分けてDIで受け取るのが基本です。`validate()`を`OrderValidator`という別Beanへ移し、コンストラクタで注入して呼ぶように直すと、次のように`validate()`にもアドバイスが効きます。

```
package demo;

import org.springframework.stereotype.Component;

@Component
public class OrderValidator {
    public void validate(String item) {
        System.out.println("  [本処理] validate(" + item + ")");
        if (item.isEmpty()) throw new IllegalArgumentException("item is empty");
    }
}
```

```
package demo;

import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private final OrderValidator validator;

    public OrderService(OrderValidator validator) {
        this.validator = validator;
    }

    public String placeOrder(String item) {
        System.out.println("  [本処理] placeOrder(" + item + ")");
        validator.validate(item); // 別Bean経由＝プロキシを通る
        return "OK:" + item;
    }
}
```

ここで注意が要るのは、先の`LoggingAspect`のポイントカットが`demo.OrderService`だけを対象にしている点です。`OrderValidator`へ移した`validate()`にアドバイスを当てるには、ポイントカットの対象にも`OrderValidator`を加えます。確認には`LoggingAspect`の代わりに次の`TimingAspect`を置き、`main`からは`placeOrder("book")`だけを呼びました。

```
package demo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TimingAspect {
    @Around("execution(public * demo.OrderService.*(..)) || execution(public * demo.OrderValidator.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("Around(前) " + pjp.getSignature().getName());
        try {
            return pjp.proceed();
        } finally {
            System.out.println("Around(後) " + pjp.getSignature().getName());
        }
    }
}
```

実行結果：

```
Around(前) placeOrder
  [本処理] placeOrder(book)
Around(前) validate
  [本処理] validate(book)
Around(後) validate
Around(後) placeOrder
```

コンストラクタ注入の書き方は[SpringのDI（依存性注入）とは？](/tech/details/2303/)で説明しています。[Springのリファレンス（Proxying Mechanisms）](https://docs.spring.io/spring-framework/reference/core/aop/proxying.html)は、この「自己呼び出しをなくす」リファクタリングを最も侵襲の少ない方法とし、次善策として自分自身のプロキシをBeanとして注入して呼ぶself injectionを挙げています。`AopContext.currentProxy()`で自分のプロキシを取り出す方法は、プロキシを公開する設定（`exposeProxy`）が必要なうえ業務コードがSpring AOPに縛られるため、リファレンス自身が強く非推奨としています。クラスを分けられない事情があり、自己呼び出しにも効かせる必要があるなら、AspectJのウィービングに切り替えるのが筋です。

## AOPに任せる処理と任せてはいけない処理

AOPは「コードに書いていない処理が実行される」仕組みです。便利さと引き換えに、業務クラスを読んでも挙動の全体が分からなくなります。そのため、アスペクトに入れてよいのは**多くのクラスに同じ形で現れ、適用箇所をアノテーションやポイントカットで明示できる横断的処理**に限り、個々の業務ルールは業務クラスに書く、と線を引くのが安全です。トランザクションや認可は処理の結果を左右しますが、`@Transactional`・`@PreAuthorize`がメソッドに付いていれば読み手は適用に気づけるため、この基準を満たします。

| 処理            | AOP向き  | 理由                              |
| ------------- | ------ | ------------------------------- |
| メソッドの開始・終了ログ  | 向く     | 全クラスで同じ形の処理                     |
| 処理時間の計測       | 向く     | 既製品（`@Timed`）もある                |
| トランザクション境界    | 既製品を使う | `@Transactional`で足りる            |
| 認可チェック        | 既製品を使う | Spring Securityの`@PreAuthorize` |
| 割引率の計算・入力値の補正 | 向かない   | 業務ルールが見えなくなる                    |
| 特定条件で処理を止める分岐 | 向かない   | 呼び出し側から原因が追えない                  |

自作する前に、Springが提供するアノテーションで済まないかを確認してください。トランザクション（`@Transactional`）、キャッシュ（`@Cacheable`）、非同期実行（`@Async`）、メソッド単位の認可（`@PreAuthorize`）、再試行（Spring Framework 7の`@Retryable`）は、既定のプロキシ方式では、いずれもプロキシによるAOPで動きます（`@Transactional`などはAspectJ方式も選べます）。仕組み自体の実装と保守はフレームワークが担うので、アプリ側で確かめるのは自分の使い方で意図どおりに効くかどうかだけになります。自作のアスペクトが本当に必要なのは、監査ログの形式を社内で統一したい、独自の計測タグを付けたい、といった既製品に無い要件があるときです。

避けるべき典型は、`@Around`で戻り値や引数を書き換える使い方です。たとえば「金額を丸めるアスペクト」を作ると、サービスのテストはアスペクト抜きでは期待値と合わず、別のBeanから呼んだときと自己呼び出しのときで結果が変わります。ポイントカットを`execution(* com.example..*(..))`のように広く取ると意図しないBeanまでプロキシ化されるので、パッケージやアノテーションで対象を絞ります。

## よくある質問

### AOPとは何の略ですか？

プログラミングの文脈ではAspect-Oriented Programming（アスペクト指向プログラミング）の略です。経営分野ではAnnual Operating Plan（年度事業計画・年間予算）、食品分野ではフランスなどの原産地呼称（Appellation d’Origine Protégée）の略としても使われ、文脈で意味が変わります。

### AOPとオブジェクト指向（OOP）の違いは何ですか？

OOPは業務の単位でクラスを分ける手法で、AOPはクラスをまたいで散らばるログやトランザクションのような処理を別に切り出す手法です。対立するものではなく、Springのように両方を組み合わせて使うのが一般的です。

### Spring AOPを使うのにAspectJは必要ですか？

AspectJのコンパイラやjavaagentは不要です。この記事で扱う`@Aspect`形式で書く場合は、アノテーションとポイントカット式の解析に`aspectjweaver`のjarを使うため、Spring Boot 4では`spring-boot-starter-aspectj`を依存に追加します。Spring独自の`Advisor`や`MethodInterceptor`をAPIで組み立てる方式なら、AspectJのjarは使いません。

### @Aroundと@Beforeはどう使い分けますか？

本処理の前にログを出す・引数を確認するだけなら`@Before`を使います。処理時間の計測やリトライのように本処理の前後をまとめて扱う場合、または戻り値や例外を加工する場合だけ`@Around`を使い、`proceed()`の呼び忘れに注意します。

### AOPのターゲットとは何ですか？

アドバイスを適用される元のオブジェクトのことです。Spring AOPではプロキシが包んでいる本物のBeanを指し、ポイントカット式の`target(...)`はこのオブジェクトの型で対象を絞り込みます。

## 関連記事

- [Spring Frameworkとは？特徴・DI/AOPとSpring Bootとの違い・最新版7を解説【2026年最新】](/tech/details/3867/)
- [SpringのDI（依存性注入）とは？仕組みと3つの注入方法をわかりやすく解説](/tech/details/2303/)
- [IoCコンテナとは？制御の反転の仕組みとSpring IoCコンテナ・Beanをわかりやすく解説](/tech/details/3210/)
- [Spring Bootの主要なアノテーション一覧｜DI・MVC・JPA・バリデーション・AOPを役割別に整理](/tech/details/4048/)
- [Spring Bootとは｜4.1系の依存名の変更と最小Web APIの作り方](/tech/details/17478/)

---

出典: [AOP（アスペクト指向プログラミング）とは？Spring AOPの仕組みと使い方・自己呼び出しの落とし穴](<https://www.issoh.co.jp/tech/details/3422/>)（株式会社一創）
