programing

Spring의 순환 종속성

nasanasas 2020. 8. 30. 08:42
반응형

Spring의 순환 종속성


Spring은 이것을 어떻게 해결합니까? 빈 A는 빈 B에 의존하고 빈 B는 빈 A에 의존합니다.


다른 답변에서 말했듯이 Spring은 콩을 만들고 필요에 따라 주입하여 처리합니다.

결과 중 하나는 빈 주입 / 속성 설정이 XML 와이어 링 파일이 의미하는 것과 다른 순서로 발생할 수 있다는 것입니다. 따라서 속성 setter가 이미 호출 된 다른 setter에 의존하는 초기화를 수행하지 않도록주의해야합니다. 이를 처리하는 방법은 빈을 InitializingBean인터페이스 구현으로 선언하는 것 입니다. 이를 위해서는 afterPropertiesSet()메서드 를 구현 해야하며 여기서 중요한 초기화를 수행합니다. (또한 중요한 속성이 실제로 설정되었는지 확인하는 코드를 포함합니다.)


봄 참조 설명서는 순환 종속성을 해결하는 방법을 설명합니다. 빈은 먼저 인스턴스화 된 다음 서로 주입됩니다.

이 클래스를 고려하십시오.

package mypackage;

public class A {

    public A() {
        System.out.println("Creating instance of A");
    }

    private B b;

    public void setB(B b) {
        System.out.println("Setting property b of A instance");
        this.b = b;
    }

}

그리고 비슷한 클래스 B:

package mypackage;

public class B {

    public B() {
        System.out.println("Creating instance of B");
    }

    private A a;

    public void setA(A a) {
        System.out.println("Setting property a of B instance");
        this.a = a;
    }

}

이 구성 파일이있는 경우 :

<bean id="a" class="mypackage.A">
    <property name="b" ref="b" />
</bean>

<bean id="b" class="mypackage.B">
    <property name="a" ref="a" />
</bean>

이 구성을 사용하여 컨텍스트를 만들 때 다음 출력이 표시됩니다.

Creating instance of A
Creating instance of B
Setting property a of B instance
Setting property b of A instance

때 주 a에 주입 b, a아직 완전히 초기화되지 않습니다.


코드베이스에서 (100 만 + 코드 줄) 작업중인 코드베이스에서 약 60 초의 긴 시작 시간에 문제가있었습니다. 우리는 12000+ FactoryBeanNotInitializedException 을 받았습니다 .

내가 한 일은 AbstractBeanFactory # doGetBean에 조건부 중단 점을 설정 한 것입니다.

catch (BeansException ex) {
   // Explicitly remove instance from singleton cache: It might have been put there
   // eagerly by the creation process, to allow for circular reference resolution.
   // Also remove any beans that received a temporary reference to the bean.
   destroySingleton(beanName);
   throw ex;
}

where it does destroySingleton(beanName) I printed the exception with conditional breakpoint code:

   System.out.println(ex);
   return false;

Apparently this happens when FactoryBeans are involved in a cyclic dependency graph. We solved it by implementing ApplicationContextAware and InitializingBean and manually injecting the beans.

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class A implements ApplicationContextAware, InitializingBean{

    private B cyclicDepenency;
    private ApplicationContext ctx;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        ctx = applicationContext;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        cyclicDepenency = ctx.getBean(B.class);
    }

    public void useCyclicDependency()
    {
        cyclicDepenency.doSomething();
    }
}

This cut down the startup time to around 15 secs.

So don't always assume that spring can be good at solving these references for you.

For this reason I'd recommend disabling cyclic dependency resolution with AbstractRefreshableApplicationContext#setAllowCircularReferences(false) to prevent many future problems.


It just does it. It instantiates a and b, and injects each one into the other (using their setter methods).

What's the problem?


Problem ->

Class A {
    private final B b; // must initialize in ctor/instance block
    public A(B b) { this.b = b };
}


Class B {
    private final A a; // must initialize in ctor/instance block
    public B(A a) { this.a = a };
 }

// Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'A': Requested bean is currently in creation: Is there an unresolvable circular reference?

Solution 1 ->

Class A {
    private B b; 
    public A( ) {  };
    //getter-setter for B b
}

Class B {
    private A a;
    public B( ) {  };
    //getter-setter for A a
}

Solution 2 ->

Class A {
    private final B b; // must initialize in ctor/instance block
    public A(@Lazy B b) { this.b = b };
}

Class B {
    private final A a; // must initialize in ctor/instance block
    public B(A a) { this.a = a };
}

From the Spring Reference:

You can generally trust Spring to do the right thing. It detects configuration problems, such as references to non-existent beans and circular dependencies, at container load-time. Spring sets properties and resolves dependencies as late as possible, when the bean is actually created.


The Spring container is able to resolve Setter-based circular dependencies but gives a runtime exception BeanCurrentlyInCreationException in case of Constructor-based circular dependencies. In case of Setter-based circular dependency, the IOC container handles it differently from a typical scenario wherein it would fully configure the collaborating bean before injecting it. For eg., if Bean A has a dependency on Bean B and Bean B on Bean C, the container fully initializes C before injecting it to B and once B is fully initialized it is injected to A. But in case of circular dependency, one of the beans is injected to the other before it is fully initialized.


Say A depends on B, then Spring will first instantiate A, then B, then set properties for B, then set B into A.

But what if B also depends on A?

My understanding is: Spring just found that A has been constructed (constructor executed), but not fully initialized (not all injections done), well, it thought, it's OK, it's tolerable that A is not fully initialized, just set this not-fully-initialized A instances into B for now. After B is fully initialized, it was set into A, and finally, A was fully initiated now.

In other words, it just expose A to B in advance.

For dependencies via constructor, Sprint just throw BeanCurrentlyInCreationException, to resolve this exception, set lazy-init to true for the bean which depends on others via constructor-arg way.


Its clearly explained here. Thanks to Eugen Paraschiv.

Circular dependency is a design smell, either fix it or use @Lazy for the dependency which causes problem to workaround it.


If you generally use constructor-injection and don't want to switch to property-injection then Spring's lookup-method-injection will let one bean lazily lookup the other and hence workaround the cyclic dependency. See here: http://docs.spring.io/spring/docs/1.2.9/reference/beans.html#d0e1161


Constructor Injection fails when there is Circular Dependency between spring beans. So in this case we Setter injection helps to resolve the issue.

Basically, Constructor Injection is useful for Mandatory dependencies, for optional dependencies better to use Setter injection because we can do re-injection.


By using Setter Injection or Field Injection or by using @Lazy for the dependency.


If two beans are dependent on each other then we should not use Constructor injection in both the bean definitions. Instead we have to use setter injection in any one of the beans. (of course we can use setter injection n both the bean definitions, but constructor injections in both throw 'BeanCurrentlyInCreationException'

Refer Spring doc at "https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource"

참고URL : https://stackoverflow.com/questions/3485347/circular-dependency-in-spring

반응형