백엔드 엔지니어 이재혁
[Spring] Spring의 주입 방식 본문
Spring Framework에는 생성자 주입, 필드 주입, Setter 주입 방식이 있다.
이 중 권장되는 방식은 생성자 주입 방식이고, 런타임에 의존 관계를 수정해야 할때 사용하는 방식이 Setter 주입 방식이다.
순환 참조 문제
필드 주입과 Setter 주입에는 순환 참조를 사전에 확인하기 어렵다는 문제가 있다.
우선, 생성자 주입을 사용하는 경우를 확인해보자.
생성자 주입은 의존 관계를 따라 가장 먼저 필요한 빈부터 생성해준다.
의존하는 객체가 먼저 생성되기 때문에 순환 의존 문제가 있는 경우 빈 생성 단계에서 즉시 예외가 발생해 사전 예방이 가능하다.
A를 생성하려면 B가 필요하고 B를 생성하려면 A가 필요하다면
A를 생성할 때는 B가 필요해서 A를 생성하지 못한다.
B를 생성할 때는 A가 필요해서 B를 생성하지 못한다.
→ 순환 의존(참조) 문제가 발생하면 스프링 컨테이너의 빈 등록 및 의존성 주입 단계에서 바로 확인할 수 있다.
그에 반해 필드/Setter 주입 방식은 순환 참조에 대해 확실한 예외를 던저주지 않는다.
필드/Setter 주입은 우선 빈을 생성하고 나서 의존성을 주입해주기 때문에 생성자 주입과 달리 의존 관계에 문제가 있어도 일단 객체들을 생성한 뒤에 주입해준다. 다음과 같은 순환 참조 문제를 확인해보자.
- ServiceA (serviceB에 의존)
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
public void run() {
serviceB.run();
}
}
- ServiceB (ServiceA에 의존)
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
public void run(){
serviceA.run();
}
}
출처: https://lordofkangs.tistory.com/409
메서드에서 순환 참조가 발생하고 있다. 의존성 주입이 된다고 해도 프로그램 실행 단계에서 해당 메서드가 실행될 때가 되서야 무한호출 문제, 즉 `StackOverflowError`가 터져 순환참조 문제를 발견하게 된다.
Setter 주입
Setter 주입은 "선택적 주입"이 필요할 때 Setter 주입을 사용한다.
어플리케이션 런타임 도중에 구현체를 교체해야 하는 경우 (사용자 언어를 바꾼다던가?)
생성자 주입 방식을 사용한다면, 기존 객체를 지우고 새 객체를 생성하면서 구현체를 새로 주입해줘야 할 것이다.
Setter 주입 방식은 객체를 지우지 않고 의존하는 구현체를 교체할 수 있는 방식이다.
필드 주입과 Setter 주입의 차이
Setter 주입 대비 필드 주입의 문제점들은 여럿 있다.
1. 캡슐화 원칙 위반
우선, 필드 주입은 Java의 Reflection API를 사용해 주입한다는 점에서 객체 지향의 캡슐화 원칙을 위반하고 있다. 캡슐화를 위해 private 접근 제어자를 적용했지만 주입을 위해 그 접근 제어자를 무시하는 방향으로 작동한다니...
Reflection API는 실무에서 어쩔 수 없이 런타임에 클래스의 상태를 확인해야 할 때, 접근 제어자를 무시하고 인스턴스에 접근할 수 있는 기능으로 알고 있다. 원칙적으로는 Reflection API를 사용하지 않는 편이 좋다.
(원칙 말고도 실제 성능상 런타임에 클래스를 분석하는 것은 느리다던가 하는 다양한 문제가 있다.)
2. 유연성이 떨어짐
Setter 주입에서 설명했듯이, Setter 주입은 객체가 생성된 후에 의존성을 변경할 수 있다.
필드 주입 방식은 런타임에 의존성 변경이 불가능하다. 굳이 하자면 Reflection API에 의존해야 한다.
3. 테스트 작성 어려움
필드 주입을 사용하면 의존성 주입을 위해 Spring 컨테이너가 반드시 필요해진다. (private 필드이므로)
Setter 주입을 사용한다면, 테스트 코드에서 Setter 메서드를 사용해 직접 주입해줄 수 있다. (public 메서드이므로)
단위 테스트마다 Spring 컨테이너를 로드해야 한다면 너무 비효율적일 것이다.
결론
의존성을 런타임에 변경해야 하는 일은 거의 없다.
이런 경우가 있는 경우에만 Setter 주입을 사용하고, 가능하면 생성자 주입을 사용하도록 하자.
생성자 주입은 객체 의존 불변성을 확보하여 프로그램의 안정성을 높일 수 있다.
'Java' 카테고리의 다른 글
| [Java] CAS 연산 활용 (1) | 2025.06.09 |
|---|---|
| [Java] Atomic과 CAS 연산 (0) | 2025.06.09 |
| [Spring] Spring이 작동하는 방식 (0) | 2025.06.08 |
| [Spring] Spring이 등장한 이유 (0) | 2025.06.08 |
| [Java] BlockingQueue (0) | 2025.05.29 |