0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Spring循環参照

Posted at

Springの依存性の注入(Dependency Injection:DI)

まずは、SpringのDIの方式を一回振り返って見ます。
SpringのDIは@Autowiredアノテーションで実現していますので、@AutowiredのJavaDocに「Marks a constructor, field, setter method」記述があって、3方式があります。

/**
 * Marks a constructor, field, setter method, or config method as to be autowired by
 * Spring's dependency injection facilities. This is an alternative to the JSR-330
 * {@link javax.inject.Inject} annotation, adding required-vs-optional semantics.
 *
 * 略
 */
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

	/**
	 * Declares whether the annotated dependency is required.
	 * <p>Defaults to {@code true}.
	 */
	boolean required() default true;
}

Field Injection (非推奨)

イメージとしてはクラスのFieldにAutowired Annotationを付けてDIする

@Component
public class AClass {
    @Autowired
    private BClass bClass;
}

ソースはきれいになりますが、下記のデメリットがあるのに、推奨ではありません。

  • 起動時に循環参照エラーが検知できずに、該当クラスを使うときに循環参照のエラーが検知できます。
  • 外部からクラス間の依存性は見えない(※Contrcutor DIとSetter DIはパラメータから見えますが、Field DIはクラス内部のprivate fieldは見えない)
  • クラス間のDI順番コントロールし難しい
@Component
public class AClass {
    @Autowired
    private BClass bClass;

    public AClass() {
        // ここはNullPointerExceptionが発生する
        System.out.println(bClass.toString())
    }
}

##Constructor Injection(Spring 4.xから推奨)
イメージとしては依存するクラスをConstructorのParamterとして、Autowired AnnotationをConstructorに付けてDIする

@Component
public class AClass {
    private final BClass bClass;

    @Autowired
    public AClass(BClass bclass) {
        this.bClass = bClass;
    }
}
  • DIしたいクラスが5個以上の場合、Constructorのパラメータが長くなってしまう
  • 循環参照は発生する可能性がある

Setter Injection

イメージとしては依存するクラスのSetterメソッドを用意して、該当メソッドにAutowired AnnotationをConstructorに付けてDIする

@Component
public class AClass {
    private BClass bClass;

    @Autowired
    public void setBClass(BClass bclass) {
        this.bClass = bClass;
    }
}

Spring3.x時に、公式サイトに推奨の書き方ですが、依頼するクラス数が多いときに、setterメソッドはいっぱい用意必要があって、ソースは読みずらいになってしまう

循環参照

循環参照とはクラス間互いに依存することになります。下記の2図示す通り。

クラス間の循環参照

参照は「AClass → BClass → CClass → AClass」になって、循環参照となっています。

circular_dependency.png

単一クラス

クラスは自分自身を参照するケースもあります。
single_class_circular.png

循環参照エラー

公式サイトのドキュメントに書きの記載があります
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependency-resolution
コンストラクターでInjectionの場合、循環参照は解決できなくて、代わりに、SetterのInjectionで変更が必要となります。

Circular dependencies
If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.

For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a BeanCurrentlyInCreationException.

One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.

Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken-and-egg scenario).

循環参照の例

  • AClassを定義する
@Component
public class AClass {
    private final BClass bClass;

    @Autowired
    public AClass(BClass bClass) {
        this.bClass = bClass;
    }
}
  • BClassを定義する
@Component
public class BClass {
    private final CClass cClass;

    @Autowired
    public BClass(CClass cClass) {
        this.cClass = cClass;
    }
}
  • CClassを定義する
@Component
public class CClass {
    private final AClass aClass;

    @Autowired
    public CClass(AClass aClass) {
        this.aClass = aClass;
    }
}
  • Springを起動する
public class AppStartup {
    public static void main(String[] args) {
        // load context
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        ctx.getBean(AClass.class);
        System.out.println("OK!");
    }
}
  • 下記のエラーが発生しまいました。
co.jp.daker.AppStartup
11月 14, 2021 12:04:28 午前 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'AClass' defined in file [D:\WorkDir\git_dir\spring-ioc\target\classes\co\jp\daker\component\AClass.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'BClass' defined in file [D:\WorkDir\git_dir\spring-ioc\target\classes\co\jp\daker\component\BClass.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'CClass' defined in file [D:\WorkDir\git_dir\spring-ioc\target\classes\co\jp\daker\component\CClass.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AClass': Requested bean is currently in creation: Is there an unresolvable circular reference?
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'AClass' defined in file [D:\WorkDir\git_dir\spring-ioc\target\classes\co\jp\daker\component\AClass.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'BClass' defined in file [D:\WorkDir\git_dir\spring-ioc\target\classes\co\jp\daker\component\BClass.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'CClass' defined in file [D:\WorkDir\git_dir\spring-ioc\target\classes\co\jp\daker\component\CClass.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AClass': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
	at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:93)
	at co.jp.daker.AppStartup.main(AppStartup.java:11)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'BClass' defined in file [D:\WorkDir\git_dir\spring-ioc\target\classes\co\jp\daker\component\BClass.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'CClass' defined in file [D:\WorkDir\git_dir\spring-ioc\target\classes\co\jp\daker\component\CClass.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AClass': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
	... 14 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'CClass' defined in file [D:\WorkDir\git_dir\spring-ioc\target\classes\co\jp\daker\component\CClass.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AClass': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:229)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1372)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1222)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
	... 28 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AClass': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1380)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
	... 42 more

Process finished with exit code 1

発生の流れを整理する

まずはJava Objectのイスタンス化と初期化を説明します。

public Class SampleObject {
    private Map<String, Object> field;

    public void setField(Map<String, Object> field) {
        this.field = field;
    }
}
  • インスタンス化

    インスタンス化とは、クラス定義を元に、実体を生成することです。例えば、上記のSampleObjectに対して、new SampleObject()でインスタンスを作成して、fieldなどはまだ初期値(null)となっている。

  • 初期化

初期化とは、簡単に言えば中身のからっぽな変数に対し、値を代入することです。例えば、上記のSampleObjectに対して、できたインスタンスでsetFieldを読んで入れたい初期値をfieldに設定する。

di_sequence.png

1.Aのインスタンスを作成するために、Aのコンストラクターを実行する必要があります
2.上記のAのインスタンスを作成するために、Aのコンストラクターの各パラメータクラスのインスタンスが作成する必要があるので、Bのインスタンス作成はAのインスタンス作成の前提となっている
3.Bのインスタンス作成前提はCのインスタンスを作成する
4.Cのインスタンス作成前提はAのインスタンスを作成する
5.上の作成を整理すると、上記の「クラス間の循環参照」の図となるので、循環参照のエラーが出ました。

Setter DIでエラー解消

上記のコンストラクターDIからSetter DIするように修正したら、エラーが解消できます。

  • AClassを定義する
@Component
public class AClass {
    private BClass bClass;

    public AClass() {
        System.out.println("AClass Constructor.");
    }

    @Autowired
    public void setBClass(BClass bClass) {
        System.out.println("set BClass.");
        this.bClass = bClass;
    }
}
  • BClassを定義する
@Component
public class BClass {
    private CClass cClass;

    public BClass() {
        System.out.println("BClass Constructor.");
    }

    @Autowired
    public void setCClass(CClass cClass) {
        System.out.println("set CClass.");
        this.cClass = cClass;
    }
}
  • CClassを定義する
@Component
public class CClass {
    private AClass aClass;

    public CClass() {
        System.out.println("CClass Constructor.");
    }

    @Autowired
    public void setAClass(AClass aClass) {
        System.out.println("set AClass.");
        this.aClass = aClass;
    }
}
  • AppStartupは正常に起動できることが確認できている
AClass Constructor.
BClass Constructor.
CClass Constructor.
set AClass.
set CClass.
set BClass.
OK!

Process finished with exit code 0

Setter DIの流れは下記となる

setter_dependency.png

循環参照エラー発生状況のマトリックス

※クラスAとクラスB互いに依頼するケース(クラスの初期化について、クラスAは先にロードする)

クラス間参照状況 DI方式 循環参照成功可否
AとB循環参照 両方ともSetter DIで
AとB循環参照 両方ともField DIで
AとB循環参照 両方ともConstructor DIで ×
AとB循環参照 A中にBはSetter DIで、B中にAはConstructor DIで
AとB循環参照 A中にBはConstrucotr DIで、B中にAはSetter DIで ×
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?