Spring
의존성 주입 3가지 방식에 대해서 설명해주세요
Spring의 생성자 주입, 세터 주입, 필드 주입의 차이와 각 방식의 장단점을 인터랙티브 시각화로 비교합니다. 왜 생성자 주입이 권장되는지, 순환 참조 문제와 불변성 보장까지 면접 완벽 대비.
2026년 3월 21일 · 약 10분 읽기
Q. "Spring에서 의존성 주입 방식 3가지를 설명하고, 왜 생성자 주입이 권장되는지 말씀해주세요."
예상 꼬리질문
답변 가이드
"Spring에서 의존성 주입(Dependency Injection)은 생성자 주입, 세터 주입, 필드 주입 세 가지 방식으로 할 수 있습니다.생성자 주입은 생성자 파라미터로 의존 객체를 받고, 세터 주입은 setter 메서드에 @Autowired를 붙이며,필드 주입은 필드에 직접 @Autowired를 붙입니다."
"생성자 주입을 권장하는 가장 큰 이유는 세 가지입니다. 첫째, 필드에 final을 선언해 불변성을 보장할 수 있습니다. 둘째, 순환 참조를 애플리케이션 시작 시점에 즉시 감지합니다. 셋째, Spring 컨테이너 없이 순수 Java로 단위 테스트할 수 있습니다."
"실무에서는 Lombok의 @RequiredArgsConstructor와 함께 사용하는 것이 표준 패턴입니다. final 필드만 모아 생성자를 자동 생성해주어 코드가 간결해집니다. 필드 주입은 코드가 간단해 보이지만 IntelliJ에서 경고를 표시할 만큼 권장되지 않으며, 프로덕션 코드에서는 사용을 피해야 합니다."
핵심 요약 (TL;DR)
- - Spring DI 방식은 생성자 주입, 세터 주입, 필드 주입 3가지다
- - 생성자 주입이 공식 권장 방식 — final 불변성, 순환 참조 조기 감지, 테스트 용이성
- - 필드 주입은 코드는 간결하지만 테스트 어렵고 불변성 보장 불가 — 프로덕션 코드에서 비권장
- - 세터 주입은 선택적 의존성(없어도 동작 가능)에만 한정적으로 사용
- - 실무 표준: @RequiredArgsConstructor + final 필드 조합
"필드 주입 쓰면 안 되나요?"라는 질문에 "그냥 스타일 차이 아닌가요?"라고 답한다면, 면접관은 바로 꼬리질문을 던질 것입니다. 생성자 주입이 권장되는 이유에는 불변성,순환 참조 감지,테스트 용이성이라는 세 가지 구체적인 근거가 있습니다.
1. 3가지 주입 방식 한눈에 비교
꼬리질문: "생성자 주입에서 final 키워드를 사용하는 이유가 무엇인가요?"
Spring IoC 컨테이너는 의존성을 세 가지 방법으로 주입할 수 있습니다. 생성자 주입은 객체 생성 시 모든 의존성을 한 번에 받고, 세터 주입은 객체 생성 후 setter를 통해 주입하며, 필드 주입은 리플렉션(Reflection)으로 필드에 직접 값을 넣습니다.
코드 길이만 보면 필드 주입이 가장 간결하지만,불변성 보장 여부,순환 참조 감지 시점,테스트 용이성에서 큰 차이가 납니다.
3가지 주입 방식 코드 비교
탭을 클릭해 각 방식의 코드와 장단점을 확인하세요.
@Service
@RequiredArgsConstructor
public class OrderService {
private final PaymentService paymentService;
private final NotificationService notificationService;
}장점
- ✓final로 불변성 보장
- ✓순환 참조를 시작 시점에 감지
- ✓Spring 없이 단위 테스트 가능
단점
- △의존성 많아지면 생성자 인자 증가 (→ SRP 위반 신호)
2. 생성자 주입이 권장되는 3가지 이유
꼬리질문: "순환 참조(Circular Dependency)가 발생하면 각 주입 방식에서 어떻게 동작하나요?"
생성자 주입의 첫 번째 강점은 불변성(Immutability)입니다. final 키워드를 사용하면 한번 주입된 의존성은 외부에서 교체할 수 없습니다. 나머지 두 방식은 final을 사용할 수 없어 런타임에 의존성이 바뀔 위험이 있습니다.
두 번째는 테스트 용이성입니다. 생성자 주입은 Spring 컨테이너 없이 순수 Java로 객체를 생성해 테스트할 수 있습니다. 필드 주입은 리플렉션 또는 @SpringBootTest가 없으면 의존성이 null입니다.
생성자 주입이 권장되는 3가지 이유
카드를 클릭해 각 이유의 상세 설명과 코드를 확인하세요.
3. 순환 참조 — 방식별 동작 비교
꼬리질문: "필드 주입은 왜 단위 테스트가 어려운가요?"
순환 참조(Circular Dependency)는 A가 B를 필요로 하고, B가 다시 A를 필요로 하는 상황입니다. Spring Boot 2.6+부터는 기본적으로 순환 참조를 금지하지만, 방식별 동작 차이를 이해하면 오류를 빠르게 진단할 수 있습니다.
생성자 주입은 빈 생성 자체가 서로를 기다리므로 시작 시점에 즉시 오류가 납니다. 반면 필드/세터 주입은 빈 객체를 먼저 생성한 뒤 나중에 의존성을 채우기 때문에 시작은 되지만 실제 호출 시 무한 재귀가 발생합니다.
순환 참조 — 방식별 동작 비교
시뮬레이션을 실행해 순환 참조가 각 방식에서 언제 발견되는지 확인하세요.
시뮬레이션 실행 후 단계별로 나타납니다
시뮬레이션 실행 후 단계별로 나타납니다
4. @RequiredArgsConstructor — 실무 표준 패턴
꼬리질문: "Lombok의 @RequiredArgsConstructor와 생성자 주입을 어떻게 함께 사용하나요?"
실무에서 생성자 주입을 가장 간결하게 쓰는 방법은 Lombok의 @RequiredArgsConstructor입니다. 이 어노테이션은 클래스 내 final 필드 또는 @NonNull 필드를 모아 생성자를 자동으로 생성합니다.
주의할 점은 final을 빠뜨리면 해당 필드가 생성자에서 제외되어 null 주입 오류가 발생한다는 것입니다. 아래 시뮬레이션으로 직접 확인해 보세요.
@RequiredArgsConstructor — 실무 표준 패턴
Lombok이 final 필드로 생성자를 자동 생성합니다. final 누락 시뮬레이션으로 문제를 직접 확인하세요.
Before — 직접 생성자 작성
@Service
public class OrderService {
private final PaymentService paymentService;
private final NotificationService notificationService;
private final OrderRepository orderRepository;
public OrderService(
PaymentService paymentService,
NotificationService notificationService,
OrderRepository orderRepository
) {
this.paymentService = paymentService;
this.notificationService = notificationService;
this.orderRepository = orderRepository;
}
}After — @RequiredArgsConstructor
핵심: @RequiredArgsConstructor는 final 또는 @NonNull 필드만 생성자에 포함합니다. 필수 의존성에는 반드시 final을 붙이세요.
자주 발생하는 문제
퀴즈로 확인하기
개념을 제대로 이해했는지 확인해 보세요.
final 키워드와 주입 방식
// A — 필드 주입
@Autowired
private final PaymentService paymentService;
// B — 세터 주입 (필드: private final PaymentService paymentService;)
@Autowired
public void setPaymentService(PaymentService ps) {
this.paymentService = ps;
}
// C — 생성자 주입 (필드: private final PaymentService paymentService;)
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}순환 참조 감지 시점
@Service
public class ServiceA {
public ServiceA(ServiceB serviceB) { }
}
@Service
public class ServiceB {
public ServiceB(ServiceA serviceA) { }
}@RequiredArgsConstructor 동작 원리
@Service
@RequiredArgsConstructor
public class OrderService {
private final PaymentService paymentService; // (A)
private NotificationService notificationService; // (B)
@NonNull
private OrderRepository orderRepository; // (C)
private String serviceName = "order"; // (D)
}
// 이 중 생성자 파라미터에 포함되는 필드는?세터 주입이 적합한 경우
// 다음 중 세터 주입이 생성자 주입보다 적합한 경우는? // 1. 없으면 앱 전체가 동작하지 않는 핵심 DB 연결 서비스 // 2. 특정 환경(개발/운영)에서만 존재하는 선택적 알림 서비스 // 3. 보안을 위해 주입 후 절대 변경되면 안 되는 암호화 서비스 // 4. 단위 테스트에서 mock으로 쉽게 교체해야 하는 핵심 서비스
면접 체크리스트
이 항목들을 자신 있게 설명할 수 있다면 의존성 주입 질문은 준비 완료입니다.
- - 생성자 주입: final 불변성 + 순환 참조 조기 감지 + 테스트 용이 — 모든 프로덕션 코드에 사용
- - 세터 주입: 없어도 동작 가능한 선택적 의존성에만 @Autowired(required=false)와 함께 한정적 사용
- - 필드 주입: 프로덕션 코드에서 금지, 테스트 코드에서만 허용
- - 실무 표준: @RequiredArgsConstructor + final 필드 조합으로 간결하게 생성자 주입
- - 순환 참조: Spring Boot 2.6+에서 기본 금지, 발견 시 설계 개선(책임 분리)으로 해결
더 알아볼 주제
- - Spring AOP 프록시 생성 메커니즘 — BeanPostProcessor가 Bean을 프록시로 교체하는 원리
- - @Lazy 지연 초기화 — 순환 참조 해결의 임시 방편으로 사용하는 방법
- - ApplicationContext 이벤트 기반 설계 — 직접 참조 없이 컴포넌트 간 통신하는 방법
추가 학습 자료를 공유합니다.
- Spring Framework 공식 문서 — Dependency Injection — 생성자/세터/필드 주입의 공식 가이드라인과 권장 방식 설명
- Baeldung — Spring Injection Types — 세 가지 주입 방식을 코드 예시와 함께 비교하는 영문 튜토리얼
- Spring Boot 2.6 Release Notes — Circular references prohibited — 순환 참조 기본 금지 정책 변경 이력 공식 문서
의견을 들려주세요
서비스 개선에 큰 도움이 됩니다. 익명으로 자유롭게 남겨주세요.