1. 순환 의존성이란 무엇입니까?

 
순환 의존성은 빈 A가 다른 빈 B에 종속되고 빈 B도 빈 A에 종속될 때 발생합니다.Bean A → Bean B → Bean A물론 더 많은 빈을 암시할 수 있습니다.Bean A → Bean B → Bean C → Bean D → Bean E → Bean A

2. Spring에 일어나는 일

 
Spring 컨텍스트가 모든 빈을 로드할 때 완전히 작동하는 데 필요한 순서대로 빈을 생성하려고 시도합니다.
순환 의존성이 없다고 가정해 보겠습니다. 대신 다음과 같은 것이 있습니다.Bean A → Bean B → Bean CSpring은 bean C를 생성하고 bean B를 생성하고 (그리고 그것에 bean C를 주입) bean A를 생성하고 bean B를 주입한다.그러나 순환 의존성을 사용하면 Spring은 서로 의존하기 때문에 먼저 생성되어야 하는 빈을 결정할 수 없습니다. 이러한 경우 Spring은 컨텍스트를 로드하는 동안

BeanCurrentlyInCreationException

을 발생시킵니다 .

생성자 주입

을 사용할 때 Spring에서 발생할 수 있습니다 . 다른 유형의 주입을 사용하는 경우 컨텍스트 로딩이 아닌 필요할 때 의존성이 주입되므로 이 문제가 발생하지 않아야 합니다.

3. 간단한 예

 
생성자 주입을 통해 서로 의존하는 두 개의 빈을 정의해 보겠습니다.
@Component
public class CircularDependencyA {

    private CircularDependencyB circB;

    @Autowired
    public CircularDependencyA(CircularDependencyB circB) {
        this.circB = circB;
    }
}
@Component
public class CircularDependencyB {

    private CircularDependencyA circA;

    @Autowired
    public CircularDependencyB(CircularDependencyA circA) {
        this.circA = circA;
    }
}
이제 구성 요소를 스캔할 기본 패키지를 지정하는 테스트용 Configuration 클래스(

TestConfig 라고 함)를 작성할 수 있습니다.

빈이 " com.baeldung.circulardependency

" 패키지에 정의되어 있다고 가정해 보겠습니다 .
@Configuration
@ComponentScan(basePackages = { "com.baeldung.circulardependency" })
public class TestConfig {
}
마지막으로 순환 의존성을 확인하기 위해 JUnit 테스트를 작성할 수 있습니다.컨텍스트 로드 중에 순환 의존성이 감지되므로 테스트가 비어 있을 수 있습니다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {

    @Test
    public void givenCircularDependency_whenConstructorInjection_thenItFails() {
        // Empty test; we just want the context to load
    }
}
이 테스트를 실행하려고 하면 다음 예외가 발생합니다.
BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA':
Requested bean is currently in creation: Is there an unresolvable circular reference?

4. 해결 방법

 
이제 이 문제를 처리하는 가장 널리 사용되는 몇 가지 방법을 보여 드리겠습니다.

4.1. 재설계

 
순환 의존성이 있는 경우 설계 문제가 있고 책임이 잘 분리되지 않을 수 있습니다. 계층 구조가 잘 설계되고 순환 의존성이 필요하지 않도록 구성 요소를 적절하게 다시 설계해야 합니다.그러나 레거시 코드, 이미 테스트되어 수정할 수 없는 코드, 완전한 재설계를 위한 시간이나 리소스 부족 등 재설계를 수행할 수 없는 여러 가지 가능한 이유가 있습니다. 구성 요소를 다시 디자인하면 몇 가지 해결 방법을 시도할 수 있습니다.

4.2. @Lazy 사용

 
주기를 깨는 간단한 방법은 Spring에 빈 중 하나를 느리게 초기화하도록 지시하는 것입니다. 따라서 빈을 완전히 초기화하는 대신 프록시를 만들어 다른 빈에 주입합니다. 주입된 빈은 처음 필요할 때만 완전히 생성됩니다.코드로 이것을 시도하려면

CircularDependencyA

를 변경할 수 있습니다 .
@Component
public class CircularDependencyA {

    private CircularDependencyB circB;

    @Autowired
    public CircularDependencyA(@Lazy CircularDependencyB circB) {
        this.circB = circB;
    }
}
지금 테스트를 실행하면 이번에는 오류가 발생하지 않음을 알 수 있습니다.

4.3. 세터/필드 주입 사용

 
가장 인기 있는 해결 방법 중 하나이자

Spring 문서에서 제안

하는 것은 setter 주입을 사용하는 것입니다.
간단히 말해서, 생성자 주입 대신 setter 주입(또는 필드 주입)을 사용하도록 빈이 연결되는 방식을 변경하여 문제를 해결할 수 있습니다. 이런 식으로 Spring은 빈을 생성하지만 의존성은 필요할 때까지 주입되지 않습니다.따라서 setter 주입을 사용하도록 클래스를 변경하고 적절한 단위 테스트를 수행할 수 있도록 CircularDependencyB 에 다른 필드(

message

)를 추가해 보겠습니다.
@Component
public class CircularDependencyA {

    private CircularDependencyB circB;

    @Autowired
    public void setCircB(CircularDependencyB circB) {
        this.circB = circB;
    }

    public CircularDependencyB getCircB() {
        return circB;
    }
}
@Component
public class CircularDependencyB {

    private CircularDependencyA circA;

    private String message = "Hi!";

    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }

    public String getMessage() {
        return message;
    }
}
이제 단위 테스트를 약간 변경해야 합니다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {

    @Autowired
    ApplicationContext context;

    @Bean
    public CircularDependencyA getCircularDependencyA() {
        return new CircularDependencyA();
    }

    @Bean
    public CircularDependencyB getCircularDependencyB() {
        return new CircularDependencyB();
    }

    @Test
    public void givenCircularDependency_whenSetterInjection_thenItWorks() {
        CircularDependencyA circA = context.getBean(CircularDependencyA.class);

        Assert.assertEquals("Hi!", circA.getCircB().getMessage());
    }
}
이러한 어노테이션을 자세히 살펴보겠습니다.

@Bean

은 주입할 빈의 구현을 검색하는 데 이러한 메서드를 사용해야 한다고 Spring 프레임워크에 알려줍니다.그리고

@Test

어노테이션을 사용하여 테스트는 컨텍스트에서

CircularDependencyA 빈을 가져오고

CircularDependencyB

가 적절하게 주입되었는지 확인하여

메시지

속성 값을 확인합니다.

4.4. @PostConstruct 사용

 
사이클을 깨는 또 다른 방법 은 빈 중 하나에

@Autowired 를 사용하여 의존성을 주입한 다음

@PostConstruct

로 어노테이션이 달린 메서드를 사용하여 다른 의존성을 설정하는 것입니다.우리의 빈은 다음과 같은 코드를 가질 수 있습니다:
@Component
public class CircularDependencyA {

    @Autowired
    private CircularDependencyB circB;

    @PostConstruct
    public void init() {
        circB.setCircA(this);
    }

    public CircularDependencyB getCircB() {
        return circB;
    }
}
@Component
public class CircularDependencyB {

    private CircularDependencyA circA;
	
    private String message = "Hi!";

    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
	
    public String getMessage() {
        return message;
    }
}
그리고 이전에 했던 것과 동일한 테스트를 실행할 수 있으므로 순환 의존성 예외가 여전히 발생하지 않고 의존성이 올바르게 주입되었는지 확인합니다.

4.5. ApplicationContextAwareInitializingBean 구현

 

빈 중 하나가 ApplicationContextAware

를 구현 하면 빈은 Spring 컨텍스트에 액세스할 수 있으며 다른 빈을 추출할 수 있습니다.

InitializingBean

을 구현함으로써 우리는 이 빈이 모든 속성이 설정된 후에 몇 가지 작업을 수행해야 함을 나타냅니다. 이 경우 의존성을 수동으로 설정하려고 합니다.다음은 빈에 대한 코드입니다.
@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {

    private CircularDependencyB circB;

    private ApplicationContext context;

    public CircularDependencyB getCircB() {
        return circB;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        circB = context.getBean(CircularDependencyB.class);
    }

    @Override
    public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
        context = ctx;
    }
}
@Component
public class CircularDependencyB {

    private CircularDependencyA circA;

    private String message = "Hi!";

    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }

    public String getMessage() {
        return message;
    }
}
다시, 이전 테스트를 실행하고 예외가 발생하지 않았으며 테스트가 예상대로 작동하는지 확인할 수 있습니다.

5. 결론

 
Spring에서 순환 의존성을 처리하는 방법에는 여러 가지가 있습니다.순환 의존성이 필요 없도록 먼저 빈을 재설계하는 것을 고려해야 합니다. 순환 의존성은 일반적으로 개선할 수 있는 설계의 증상이기 때문입니다.그러나 프로젝트에 순환 의존성이 절대적으로 필요한 경우 여기에 제안된 몇 가지 해결 방법을 따를 수 있습니다.
선호되는 방법은 세터 주입을 사용하는 것입니다. 그러나 일반적으로 Spring이 빈의 초기화 및 주입을 관리하는 것을 중지하고 다른 전략을 사용하여 이를 스스로 수행하는 것을 기반으로 하는 다른 대안이 있습니다.이 기사의 예제는

GitHub 프로젝트

에서 찾을 수 있습니다 .
Generic footer banner