【Spring Boot】循環参照エラーをやさしく解説

Java入門・実践
スポンサーリンク
スポンサーリンク

はじめに

Spring Boot を使っていると、
「循環参照エラー」 に出会うことがあります。

  • A が B を @Autowired
  • B が A を @Autowired
  • 起動時に落ちる
  • 「Requested bean is currently in creation」と言われる

こういったエラーは、
DI(依存注入)の仕組み を理解すると一気に解決します。

この記事では、循環参照エラーの原因と対処法を
やさしく・実務寄り で整理していきます。


循環参照とは

一言でいうと、

「お互いが依存し合っていて、どちらも先に作れない状態」

です。

Java
@Service
public class AService {
    @Autowired
    private BService b;
}

@Service
public class BService {
    @Autowired
    private AService a;
}

A を作るには B が必要
B を作るには A が必要
→ どちらも作れない
→ 起動エラー

という流れです。


よく出るエラーメッセージ

Requested bean is currently in creation

Error creating bean with name 'aService':
Requested bean is currently in creation: Is there an unresolvable circular reference?

UnsatisfiedDependencyException

Unsatisfied dependency expressed through field 'aService'

どちらも 循環参照が原因 です。


循環参照が起きるパターン(実務で多い順)

Service 同士が相互に依存している

最も多いパターンです。

Controller → Service → Repository → Controller のように戻ってくる

設計の問題。

A → B → C → A のように 3 つ以上で循環

気づきにくいパターン。

コンストラクタ DI で循環

コンストラクタ DI は循環に弱い。


原因を理解する

Spring は Bean を作るときに、

  1. AService を作ろうとする
  2. BService が必要
  3. BService を作ろうとする
  4. AService が必要
  5. でも AService はまだ作成中
  6. → 無限ループになるためエラー

という流れになります。


対処法(全体像)

1. 設計を見直す(最重要)
2. 依存関係を片方向にする
3. 片方を setter / フィールド DI に変える
4. インターフェースを挟む
5. @Lazy を使う(最終手段)

設計を見直す(最も正しい解決法)

循環参照は 設計の匂い(Bad Smell) です。

よくある改善

  • AService が BService を呼ぶだけにする
  • BService の処理を AService に寄せる
  • 共通処理を CService に切り出す

Java
A → B → A になっているなら、
A → B に一本化する

依存関係を片方向にする

Before

A → B
B → A

After

A → B

片方向にするだけで循環は解消します。

片方を setter / フィールド DI に変える

コンストラクタ DI は循環に弱いですが、 setter DI なら後から注入できるため循環を回避できます。

Before(循環発生)

Java
public AService(BService b) { ... }
public BService(AService a) { ... }

After(循環回避)

Java
@Service
public class AService {
    private BService b;

    @Autowired
    public void setB(BService b) {
        this.b = b;
    }
}

※ 設計改善が最優先で、これは応急処置です。

4. インターフェースを挟む

依存関係を抽象化することで循環を解消できます。

Before

AService → BService
BService → AService

After

AService → BInterface
BService → AInterface

@Lazy を使う(最終手段)

どうしても循環を解消できない場合に使います。

Java
@Autowired
@Lazy
private AService a;

Spring が遅延ロードするため、 循環が解消されます。

ただし、設計改善が最優先 です。


実務でのチェックリスト

A と B が相互に依存していないか

Service 同士の相互依存は危険。

コンストラクタ DI で循環していないか

setter に変えると解決することも。

共通処理を別クラスに切り出せないか

CService を作ると循環が消える。

@Profile で片方が無効になっていないか

片方だけ有効 → 依存が解決できない。

@Lazy を使う必要が本当にあるか

最終手段。


ログの読み方(おさらい)

循環参照のときは、
ログの最後にこう出ます。

Requested bean is currently in creation
Is there an unresolvable circular reference?

この 2 行が見えたら、
循環参照が原因で確定 です。


まとめ

循環参照エラーは、

「お互いが依存し合っていて、どちらも先に作れない」

という状態です。

  • A → B → A の依存関係
  • Service 同士の相互依存
  • コンストラクタ DI の循環
  • @Profile による片方の無効化

難しく聞こえますが、
「依存を片方向にする」
と理解できれば十分です。


decopon
decopon

循環参照エラーは最初こそ難しく見えますが、
“依存関係の流れ” を意識するだけで
落ち着いて対処できるようになります。
あなたの開発が、今日より少しだけ楽になりますように。

moco
moco

A さんと B さんがお互いに
“あなたが先に…”って譲り合ってる感じだね…

コメント

タイトルとURLをコピーしました