循環依存関係
循環依存関係は、Spring フレームワークにおける一般的な問題の 1 つであり、2 つ以上のクラスが相互に参照する場合に発生します。この場合、Spring フレームワークはどのクラスを最初にインスタンス化および初期化する必要があるかを判断できず、例外が発生します。一般的なソリューションには、コンストラクター インジェクション、セッター メソッド インジェクション、静的ファクトリ メソッド インジェクション、サードパーティ ライブラリの使用が含まれます。
今回使用したバージョン:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.0</version>
<relativePath/>
</parent>
ケース
public interface ServiceA {
}
public interface ServiceB {
}
@Service
public class ServiceAImpl implements ServiceA {
private ServiceB serviceB;
public ServiceAImpl(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceBImpl implements ServiceB {
private ServiceA serviceA;
public ServiceBImpl(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
解決
1.再設計
循環依存関係がある場合は、設計上の問題があり、責任が適切に分離されていない可能性があります。コンポーネントの階層が適切に設計され、循環依存関係が不要になるように、コンポーネントを正しく再設計するようにしてください。
コンポーネントを再設計できない場合 (レガシー コード、すでにテスト済みでコードを変更できない、完全に再設計するのに十分な時間やリソースがないなど、さまざまな理由が考えられます)、この問題を解決するための回避策がいくつかあります。 。
2.@怠け者
Spring の循環依存関係を解決する簡単な方法は、 Bean で遅延ロードを使用することです。つまり、この Bean は完全には初期化されていません。実際、彼が注入するのはプロキシであり、初めて使用されるときにのみ完全に初期化されます。
@Service
public class ServiceAImpl implements ServiceA {
private ServiceB serviceB;
public ServiceAImpl(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}
3. セッター/フィールドインジェクション
簡単に言えば、注入する必要がある Bean には、コンストラクター注入ではなく、セッター注入 (またはフィールド注入) を使用します。この方法で Bean を作成すると、この時点ではその依存関係は実際には注入されず、必要な場合にのみ注入されます。
@Service
public class ServiceAImpl implements ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
SpringBoot 2.6.x では、循環依存関係の使用を推奨しません。最も簡単な方法は、グローバル構成ファイルで循環参照を許可することです。このプロパティのデフォルト値は false で、表示ステートメントは true です。これにより、コンソールの循環参照例外を回避できます。プロジェクトが始まるとき。
spring:
main:
allow-circular-references: true
4.@PostConstruct
サイクルを断ち切るもう 1 つの方法は、注入されるプロパティ (プロパティは Bean) でそれを使用し@Autowired
、それを使用して@PostConstruct
別のメソッドをマークし、このメソッドで他の依存関係を設定することです。
@Service
public class ServiceAImpl implements ServiceA {
@Autowired
private ServiceB serviceB;
@PostConstruct
public void init() {
System.out.println(serviceB);
serviceB.setServiceA(this);
}
}
@Service
public class ServiceBImpl implements ServiceB {
private ServiceA serviceA;
public void setServiceA(ServiceA serviceA) {
System.out.println(serviceA);
this.serviceA = serviceA;
}
}
要約:
方法 | 依存 | 注入法 | 循環依存関係を解決する機能 |
---|---|---|---|
状況 1 | AB 相互依存性 | すべてセッターメソッドを使用します | できる |
状況 2 | AB 相互依存性 | どちらもコンストラクターメソッドを使用します | できない |
状況 3 | AB 相互依存性 | B を A に注入するにはセッターを使用し、A を B に注入するにはコンストラクターを使用します。 | できる |
状況 4 | AB 相互依存性 | B を A に注入するにはコンストラクターを使用し、A を B に注入するにはセッターを使用します。 | できない |
状況 5 | AB 相互依存性 | B を A に注入して使用し@Autowired 、A を B に注入して@PostConstruct + セッターを使用します |
できる |
状況 6 | AB 相互依存性 | B を A に注入する@PostConstruct + セッターを使用し、A を B に注入する@Autowired |
できる |