はじめに
Springの公式ドキュメントを読んでいたところ、循環依存(循環参照)をsetterインジェクションで解決できることを知りました
アウトプットしないと忘れてしまうので、投稿します
前提
- Spring DIとは?という内容には触れません
- 23/08/22時点でSpring Frameworkのカレントである6.0.11のリファレンスを元にしています
- アプリはspring bootアプリケーションとして動かします
循環依存とは
循環依存が発生する簡単なサンプルが以下になります
@Service
public class HogeService {
private HugaService hugaService;
// コンストラクタインジェクション
HogeService(HugaService hugaService){
this.hugaService = hugaService;
}
}
@Service
public class HugaService {
private HogeService hogeService;
// コンストラクタインジェクション
HugaService(HogeService hogeService){
this.hogeService = hogeService;
}
}
この状態でアプリをスタートさせると起動せず、以下のようなエラーが発生するかと思います。
もしくはspring bootを利用していない場合はBeanCurrentlyInCreationException
という例外がthorwされるようです(動作未確認)
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| hogeService defined in file ※ファイルパス
↑ ↓
| hugaService defined in file ※ファイルパス
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
解決方法1 setterインジェクションを利用する
公式ドキュメント #依存関係解決プロセスに解決方法の記載がありました。
Beanの注入方法をsetterインジェクションに変更することで循環依存が解決できるということでした。
ソースを変更して、動作を見てみたいと思います。
public class HogeService {
private HugaService hugaService;
- // コンストラクタインジェクション
- HogeService(HugaService hugaService){
+ // setterインジェクション
+ public void setHugaService(HugaService hugaService){
this.hugaService = hugaService;
}
}
public class HugaService {
private HogeService hogeService;
- // コンストラクタインジェクション
- HugaService(HogeService hogeService){
+ // setterインジェクション
+ public void setHogeService(HogeService hogeService){
this.hogeService = hogeService;
}
}
アプリが起動することを確認できました。
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.1.0)
2023-08-22T14:56:59.324+09:00 INFO 16884 --- [ main] c.e.s.SpringDiSampleApplication : Starting SpringDiSampleApplication using Java 17.0.2 with PID 16884 (C:\work\Spring\spring-di-sample\target\classes started by のすけ in C:\work\Spring\spring-di-sample)
2023-08-22T14:56:59.330+09:00 INFO 16884 --- [ main] c.e.s.SpringDiSampleApplication : No active profile set, falling back to 1 default profile: "default"
2023-08-22T14:57:00.664+09:00 INFO 16884 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2023-08-22T14:57:00.676+09:00 INFO 16884 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-08-22T14:57:00.676+09:00 INFO 16884 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.8]
2023-08-22T14:57:00.817+09:00 INFO 16884 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-08-22T14:57:00.819+09:00 INFO 16884 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1413 ms
2023-08-22T14:57:01.379+09:00 INFO 16884 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-08-22T14:57:01.390+09:00 INFO 16884 --- [ main] c.e.s.SpringDiSampleApplication : Started SpringDiSampleApplication in 2.56 seconds (process running for 3.008)
解決方法2 Lazyアノテーションを該当クラスに付与する
こちらもソースを変更して、動作を見てみたいと思います。
package com.example.spring_di_sample.service;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@Service
+@Lazy
public class HogeService {
private final HugaService hugaService;
package com.example.spring_di_sample.service;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@Service
+@Lazy
public class HugaService {
private final HogeService hogeService;
上記の変更でもアプリが起動できることを確認できました
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.1.0)
2023-08-22T15:05:23.433+09:00 INFO 65472 --- [ main] c.e.s.SpringDiSampleApplication : Starting SpringDiSampleApplication using Java 17.0.2 with PID 65472 (C:\work\Spring\spring-di-sample\target\classes started by のすけ in C:\work\Spring\spring-di-sample)
2023-08-22T15:05:23.440+09:00 INFO 65472 --- [ main] c.e.s.SpringDiSampleApplication : No active profile set, falling back to 1 default profile: "default"
2023-08-22T15:05:24.744+09:00 INFO 65472 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2023-08-22T15:05:24.755+09:00 INFO 65472 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-08-22T15:05:24.755+09:00 INFO 65472 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.8]
2023-08-22T15:05:24.886+09:00 INFO 65472 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-08-22T15:05:24.889+09:00 INFO 65472 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1375 ms
2023-08-22T15:05:25.366+09:00 INFO 65472 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-08-22T15:05:25.376+09:00 INFO 65472 --- [ main] c.e.s.SpringDiSampleApplication : Started SpringDiSampleApplication in 2.426 seconds (process running for 2.915)
まとめ
業務で遭遇した際は、Lazyアノテーションで対処していたのですが
リファレンスを読むことで知らなかった情報にふれることができたため、投稿しました
今回は、リファレンスを読むことの大切さを学びました
問題が発生した場合に都度 検索->解決 で対処できないこともないですが
結果として、今回のような解決方法を知る機会を失ってしまうので