스프링 컨테이너는 어느 객체부터 생성할까?
스프링은 로딩한 Bean들이 완전히 작동할 수 있는 순서대로 생성한다고 한다.
A 는 B 가 필요하니 B 부터 생성하려 하지만 B 는 C 가 필요하니 C부터 생성한다.
C → B → A 가 생성된다.
객체 A, B, C 가 있다. 이렇게 참조 방향이 순환하고 있는 형태를 순환 참조라 한다.
스프링 컨테이너가 빈을 생성하는 방법대로 생성 순서를 따져보자.
A 는 B 가 필요하다. B 는 C 가 필요하다. C 는 A 가 필요하다.
A 는 B 가 필요하다. B 는 C 가 필요하다. C 는 A 가 필요하다.
…
빈 생성이 무한 루프에 빠진다.
하지만 스프링은 순환참조를 인지하는 로직이 구현되어 있다. 단, 생성자 주입일 경우에 가능하다.
BeanA → BeanB, BeanB → BeanA 생성사 주입으로 순환 참조를 만들고 어플리케이션을 실행해보자.
@Component
public class BeanA {
private BeanB beanB;
public BeanA(BeanB beanB) {
this.beanB = beanB;
}
}
@Component
public class BeanB {
private BeanA beanA;
public BeanB(BeanA beanA) {
this.beanA = beanA;
}
}
스프링이 순환 참조를 인지하고 어플리케이션 로딩을 중단했다.
***************************
APPLICATION FAILED TO START
***************************
┌─────┐
| beanA defined in file [BeanA.class]
↑ ↓
| beanB defined in file [BeanB.class]
└─────┘
디버깅을 해보면 생성된 싱글톤 빈을 등록하는 DefaultSingletonBeanRegistry.class
의 getSigleton()
메서드 내부에서 beforeSigletonCreation(beanName)
을 호출한다.
이름을 보니 뭔가 빈을 등록하기 전 무언가를 수행하는 것 같다.
beforeSigletonCreation(beanName)
로 이동하니 2가지 조건을 충족하면
BeanCurrentlyInCreationException
예외를 뱉는다.
이미 생성중인 Bean 을 또 생성하려해서 예외를 발생시키고 있다.
inCreationCheckExclusions
은 빈 생성 중에 검사를 제외할 빈의 이름들을 담는 Set 이다.
singletonsCurrentlyInCreation
은 현재 생성 중인 빈들의 이름을 저장하는 Set 이다.
inCreationCheckExclusions
에서 beanA
는 검사 제외 대상에 없고
singletonsCurrentlyInCreation
현재 생성 중인 빈들의 이름중에 이미 beanA
가 있다.
이렇게 두 조건을 충족해서 BeanCurrentlyInCreationException
예외를 발생시켰다.
Setter 주입은 setter 가 호출되는 시점에 의존성 주입을 하기 때문에 알 수 없다.
@Component
public class BeanA {
private BeanB beanB;
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}
@Component
public class BeanB {
private BeanA beanA;
public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
}
그럼 발생할수 있는 문제는 무엇일까?
@Component
public class BeanB {
private BeanA beanA;
public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
public void call() {
beanA.call();
}
}
@Component
public class BeanA {
@Autowired
private BeanB beanB;
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
public void call(){
System.out.println("BeanA.call");
beanB.call();
}
}
beanA.setBeanB(beanB);
beanB.setBeanA(beanA);
beanA.call();
beanB.call();
순환 참조 관계에 있는 객체가 무한히 서로의 메서드를 호출할 수 있다. 역시 StackOverFlow 발생한다.
생성자 주입
장점
단점
setter 주입
장점
단점은
NPE
발생 위험이 있다.final
키워드 선언 불가능하다.field 주입
장점
단점
immutable 하지 못하다.
Autowired Fields
Fields are injected right after construction of a bean
필드에 선언된 @Autowired 어노테이션 프로세서는 객체가 생성된 직후 의존성 주입을 하기 때문에
final
키워드 선언이 불가능하다.
필드 주입 방식도 앱 구동시 순환참조 인지가 불가능하다고 봤는데 코드를 직접 짜보니
스프링이 순환 참조가 있으니 앱 구동을 중지했다. 이건 더 알아봐야겠다.
@Component
public class BeanB {
@Autowired
private BeanA beanA;
}
@Component
public class BeanA {
@Autowired
private BeanB beanB;
}
┌─────┐
| beanA (field private BeanA.beanB)
↑ ↓
| beanB (field private BeanB.beanA)
└─────┘
https://www.linkedin.com/pulse/you-should-stop-using-spring-autowired-felix-coutinho/
정보 감사합니다.