1. 빈이 등록된 다음엔?
컴포넌트 스캔을 통해 스프링은 애플리케이션 시작 시 여러 객체를 빈으로 등록합니다.
하지만 빈이 등록되었다고 해서 곧바로 애플리케이션이 동작할 수 있는 것은 아닙니다.
서비스는 리포지토리를 필요로 하고, 컨트롤러는 서비스를 필요로 합니다.
등록된 빈들을 서로 연결하는 과정을 의존관계 주입(DI)이라고 합니다.
2. 자동 의존관계 주입 @Autowired
스프링은 자동 의존관계 주입이라는 기능을 제공합니다.
이 기능을 명시적으로 표현하는 애노테이션이 @Autowired입니다.
이 애노테이션의 의미는 다음과 같습니다.
스프링 컨테이너에 등록된 빈 중에서 타입이 맞는 객체를 찾아 자동으로 주입하라
@Autowired
private MemberRepository memberRepository;
이 코드는 내부적으로 MemberRepository 타입의 빈을 컨테이너에서 찾아 해당 필드에 주입합니다.
즉, 개발자가 직접 컨테이너에서 빈을 조회하지 않아도 스프링이 대신 찾아 연결해 주는 방식입니다.
이후 내용에서 말하는 “자동 주입”은 모두 이 메커니즘을 의미합니다.
언제 @Autowired를 생략해도 될까?
생성자가 하나뿐인 경우, 스프링은 해당 생성자를 자동으로 주입 대상으로 선택합니다.
public OrderService(MemberRepository memberRepository) {
}
이 경우 @Autowired를 붙이지 않아도 의존관계 주입이 정상적으로 동작합니다.
3. 의존관계 주입 방식
스프링이 제공하는 의존관계 주입 방식은 크게 네 가지입니다.
- 필드 주입
- setter 주입
- 생성자 주입
- 일반 메서드 주입
모든 방식이 동작은 하지만, 스프링 공식 문서와 실무에서는 생성자 주입을 기본으로 권장합니다.
그 이유를 이해하려면 각 방식의 차이를 먼저 짚고 넘어가는 것이 좋습니다.
필드 주입
@Autowired
private MemberRepository memberRepository;
- 코드가 간단해 보이지만 객체 생성 시점에 의존관계가 보이지 않음
- 테스트가 어려움
setter 주입
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
- 선택적 의존관계 표현 가능
- 객체 생성 이후에도 변경 가능 (불변성 X)
- 객체가 불완전한 상태로 존재할 수 있음
생성자 주입
public OrderService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
불변성
- final 필드 사용 가능
- 객체 생성 이후 의존관계 변경 불가
필수 의존관계 보장
- 주입되지 않으면 객체 생성 실패
- 런타임 오류를 사전에 방지
명확한 설계
- 필요한 의존관계가 생성자 시그니처에 드러남
즉, 생성자 주입은 객체를 항상 완전한 상태로 만들도록 강제합니다.
이 차이 때문에 생성자 주입이 가장 안전한 방식으로 평가됩니다.
4. 의존관계 주입은 언제 일어나는가
스프링은 빈을 생성하면서 의존관계를 함께 주입합니다.
생성자 주입의 경우 생성자 호출, 의존관계 전달 이 두 과정이 동시에 일어납니다.
그래서 생성자 주입은 객체가 만들어지는 순간부터 사용 가능한 상태를 보장합니다.
5. Lombok @RequiredArgsConstructor
Lombok의 @RequiredArgsConstructor는 필수 의존관계를 가지는 생성자를 자동으로 만들어주는 기능입니다.
@RequiredArgsConstructor
@Service
public class OrderService {
private final MemberRepository memberRepository;
}
이 애노테이션은 다음 역할을 합니다.
- final이 붙은 필드를 모아 생성자 생성
- 생성자 주입을 강제
- 생성자 코드를 직접 작성할 필요 제거
6. 생성자 주입 예시
1️⃣ 역할과 구현을 먼저 나눈다
public interface MemberRepository {
void save(Member member);
}
@Repository
public class MemoryMemberRepository implements MemberRepository {
@Override
public void save(Member member) {
// 메모리에 저장
}
}
- 저장소 역할은 인터페이스로 정의
- 구현체는 스프링 빈으로 등록
2️⃣ 서비스를 생성자 주입으로 작성한다.
@Service
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public void join(Member member) {
memberRepository.save(member);
}
}
여기서 중요한 점은 다음입니다.
- new MemoryMemberRepository()가 없다.
- 어떤 구현체가 들어오는지 서비스는 모른다.
- 필요한 것은 역할(MemberRepository) 뿐이다.
3️⃣ 스프링이 실제로 해주는 일
애플리케이션 시작 시 스프링은 다음 순서로 동작합니다.
- @Repository가 붙은 MemoryMemberRepository를 빈으로 등록
- @Service가 붙은 MemberService를 빈으로 등록
- MemberService 생성자 호출
- 생성자의 파라미터 타입(MemberRepository)에 맞는 빈 탐색
- MemoryMemberRepository를 찾아 주입
new MemberService(new MemoryMemberRepository());
즉 이 코드를 스프링이 대신 작성해 주는 것과 같습니다.
4️⃣ Lombok을 사용하면
@RequiredArgsConstructor
@Service
public class MemberService {
private final MemberRepository memberRepository;
public void join(Member member) {
memberRepository.save(member);
}
}
- 생성자를 직접 쓰지 않아도 된다
- 생성자 주입이 강제된다
ps. JPA 엔티티 경우 기본 생성자 생성 필수! 이유는.. 찾아보세요 😊
'스프링 > 개념' 카테고리의 다른 글
| [빈 시리즈-1] 스프링 빈은 어떻게 등록되는가 (1) | 2026.01.09 |
|---|---|
| 싱글톤 패턴의 한계와 싱글톤 컨테이너 (1) | 2026.01.08 |
| 커서 기반 페이징(Cursor-based Pagination) (0) | 2025.05.25 |