Spring을 공부하면서 핵심 개념인 DI에 대해 좀 더 자세히 알아볼 필요를 느껴 이번 포스트를 작성했습니다.
이번 포스트에서는 자세한 구현보다는, 각 방법의 특징을 주로 살펴보려고 합니다.
DI의 종류
DI의 방법은 Constructor DI, Setter DI, Field DI 이렇게 3가지 입니다.
Spring 공식문서에서는 Field DI, Setter DI 보다는 Constructor DI를 권장하고 있습니다. ( Spring 공식 문서 : Constructor vs Setter )
Constructor DI : 생성자 주입
특징
첫 번째, Spring에 대한 의존도가 낮습니다. 생성자 주입은 DI Container의 도움 없이 생성자를 통해 의존성을 직접 주입할 수 있습니다. 즉 의존성 주입에 Spring이 필수적이지 않습니다.
두 번째, DI 방법 중 유일하게 final 키워드를 사용할 수 있습니다.
장점
1. 필수 의존성을 보장
생성자의 매개변수를 모두 입력하지 않으면, 생성자는 동작하지 않습니다. 즉 모든 매개변수 입력이 강제됩니다. 때문에 필수적인 의존성이 누락되는 것을 미리 방지할 수 있습니다.
2. final 키워드
위에서 설명했듯이 유일하게 final 키워드를 사용 있습니다. 때문에 NPE(Null Pointer Exception)이 절대 발생하지 않습니다. 다른 방법들은 final 키워드를 사용할 수 없기 때문에 NPE의 위협으로부터 안전하지 않습니다.
3. SRP 원칙 적용의 지표
만약 생성자 주입을 사용한다면, 생성자를 딱 봤을 때 얼마나 지저분한지를 통해 SRP가 잘 지켜지고 있는지 간단하게 확인할 수 있습니다.
생성자의 매개변수가 많다 ( 생성자가 지저분하다 ) -> 의존을 많이 한다 -> 하는 일이 많다 -> 책임을 많이 진다. -> SRP를 위배한다. -> 리팩토링이 필요하다.
4. 빠른 순환 의존성 발견
순환 의존성(Circular Dependency)이란?
A Class가 B Class를 의존하는데 B Class가 다시 A Class를 의존할 경우
A Class가 B Class를 의존하고, B Class가 C Class를 의존하고 C Class가 A Class를 의존하는 경우
위 경우들처럼 의존성이 순환하는 상태를 순환 의존성이라고 부릅니다.
필드, 수정자 주입의 경우 객체를 먼저 생성한 다음 의존성을 주입합니다. 따라서 순환 의존성이 존재해도 생성시에는 문제가 없습니다. 때문에 런타임에서 순환 의존성이 발견됩니다. 하지만 생성자 주입은 생성 시점에 의존성을 주입받기 때문에 어플리케이션 구동 시점에서 빠르게 순환 의존성을 발견할 수 있습니다.
하지만 스프링부트 2.6부터는 순환 의존성이 기본적으로 허용되지 않도록 변경되었습니다. 즉 다른 의존성 주입 방법들도 순환 참조가 발생한다면, 애플리케이션 구동 시점에서부터 에러가 발생합니다. ( 이러면 생성자 주입의 장점에서 빼야하나 고민했지만 일단 넣어봤습니다. )
5. 테스트 용이성💡
좋은 테스트란 신뢰성이 높고, 유지보수하기 쉽고, 문제를 빠르게 찾을 수 있는 테스트를 말합니다.
유지보수 측면
테스트가 프레임워크에 많이 의존한다면, 테스트 이해 및 유지보수를 위해 프레임워크에 대한 추가 지식 필요합니다. 또한 프레임워크 변경 시, 테스트 이식 난이도가 높아집니다.
생성자 주입은 낮은 Spring 의존도로 인해 위와 같은 단점이 적고, 유보수성이 높은 순수 자바 스타일( POJO : Plain Old Java Object )로 테스트를 작성하기 용이합니다.
또한 생성자를 통해 원하는 상황에 맞는 객체를 주입할 수 있기 때문에 단위 테스트가 편리하고 Mockito, JUnit 같은 테스트 도구와도 잘 어울립니다.
속도 측면
테스트를 하기 위해 Spring을 구동시킨다면, 컴포넌트들을 등록하고 초기화하는 등의 Spring에 소모되는 시간 때문에 테스트가 느려질 수밖에 없습니다. 때문에 빠른 테스트를 위해서는 Spring 구동이 없는 테스트를 작성해야 합니다.
유지보수때와 마찬가지로, 생성자 주입은 Spring 의존도가 낮아, Spring 구동이 필요 없는 빠른 테스트를 작성하기에 적합합니다.
Setter DI : 수정자 주입
수정자 주입의 한가지 이점은 의존성을 다시 주입할 수 있다는 것입니다.
이러한 이점은 optional dependencies, 즉 선택적으로 의존성을 부여해야 하는 상황에서 효과적입니다.
선택적, 즉 필수적이지 않기 때문에 의존성이 Null일 가능성이 있습니다. 따라서 Null Pointer Exception을 피하기 위해서는 해당 의존성을 사용하는 부분마다 Null check을 하던지, 아니면 합리적인? 적합한? 기본값으로 초기화해줘야 하는 단점이 있습니다.
다음은 그 단점에 대한 예시코드입니다.
@Component
public class OrderService {
private NotificationService notificationService;
// false : 필수적이지 않은 의존성
@Autowired(required = false)
public void setNotificationService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void placeOrder(Order order) {
// 때문에 null check이 필수적입니다.
if (notificationService != null) {
notificationService.send(order);
}
}
}
Field DI : 필드 주입
단 한줄로 간편하게 의존성을 추가할 수 있다는 장점이 있지만, 그 이외에는 거의 모든게 단점입니다.
직접 의존성을 주입하지도 못하고, final 키워드도 못쓰고, 너무 간결해서 SRP를 위배하고 있어도 티가 안나고, 테스트를 할 때도 Spring의 구동이 필수적입니다.
참고자료
Spring 공식 문서 : 새성자 DI vs 수정자 DI
backendcode 블로그
marklee1117 블로그
dahye4321 블로그
soobarkbar 블로그
mangkyu 블로그
tecoble 블로그
End.
'CS' 카테고리의 다른 글
| 프로젝트 패키지 구조 (0) | 2025.09.17 |
|---|---|
| Java final 키워드 (1) | 2025.06.12 |
| 실수계산 오차와 부동소수점 ( feat. IEEE 754 표준 ) 3부 (0) | 2025.05.21 |
| 실수계산 오차와 부동소수점 ( feat. IEEE 754 표준 ) 2부 (0) | 2025.05.16 |
| URI vs URL vs URN (0) | 2025.04.14 |