본문 바로가기
카테고리 없음

생성자 주입(Constructor Injection)이란?

by code study 2026. 5. 9.

들어가며

Spring에서 의존성 주입(DI) 방법은 세 가지가 있습니다.
그 중 가장 권장되는 방식이 바로 생성자 주입입니다.
왜 생성자 주입이 권장되는지, 불변성과 테스트 용이성이 왜 중요한지 정리해보겠습니다.


의존성 주입(DI)이란?

의존성 주입은 객체가 필요로 하는 다른 객체를 직접 생성하지 않고 외부(Spring 컨테이너)에서 주입받는 것입니다.

// 직접 생성 - 의존성 주입 아님
public class MemberService {
    private MemberRepository memberRepository = new MemberRepository(); // 직접 생성
}

// 의존성 주입 - Spring이 주입
public class MemberService {
    private final MemberRepository memberRepository; // 외부에서 주입받음
}

세 가지 의존성 주입 방법

1. 필드 주입

@Service
public class MemberService {

    @Autowired
    private MemberRepository memberRepository; // 필드에 직접 주입
}

코드가 간결하지만 불변성 보장 안 됨, 테스트 어려움 등의 단점이 있습니다.

2. 수정자(Setter) 주입

@Service
public class MemberService {

    private MemberRepository memberRepository;

    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

선택적 의존성에는 유용하지만, 주입이 누락되면 런타임 오류가 발생합니다.

3. 생성자 주입 (권장)

@Service
public class MemberService {

    private final MemberRepository memberRepository; // final 키워드

    @Autowired // 생성자가 하나면 생략 가능
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

객체 생성 시점에 모든 의존성을 주입받기 때문에 불변성 보장테스트 용이성이 뛰어납니다.


생성자 주입의 동작 원리

Spring 컨테이너가 Bean을 생성할 때 @Autowired가 붙은 생성자를 찾아 필요한 Bean을 자동으로 주입합니다.

[Spring 컨테이너 Bean 생성 과정]

1. MemberService Bean 생성 요청
        ↓
2. @Autowired 생성자 탐색
        ↓
3. 생성자 파라미터 타입(MemberRepository) 확인
        ↓
4. 컨테이너에서 MemberRepository Bean 조회
        ↓
5. MemberService 생성자 호출 (MemberRepository 주입)
        ↓
6. MemberService Bean 등록 완료

불변성 보장 (Immutability)

생성자 주입의 가장 큰 장점은 final 키워드를 사용할 수 있다는 것입니다.
final 필드는 한 번 할당되면 변경할 수 없기 때문에, 객체가 생성된 이후 의존성이 바뀌지 않습니다.

// 필드 주입 - final 사용 불가
@Service
public class MemberService {
    @Autowired
    private MemberRepository memberRepository; // final 불가, 언제든 변경 가능
}

// 생성자 주입 - final 사용 가능
@Service
public class MemberService {
    private final MemberRepository memberRepository; // final - 변경 불가

    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository; // 생성 시점에 1번만 할당
    }
}
필드 주입:     생성 → 주입 → 언제든 변경 가능 → 불안정
생성자 주입:   생성 시점에 주입 → 이후 변경 불가 (final) → 안정적

 


테스트 용이성

생성자 주입은 Spring 컨테이너 없이도 순수 Java 코드로 테스트할 수 있습니다.

필드 주입 - 테스트하기 어렵다

@Service
public class MemberService {
    @Autowired
    private MemberRepository memberRepository; // Spring이 주입해야만 사용 가능
}

// 테스트
class MemberServiceTest {
    @Test
    void test() {
        MemberService service = new MemberService();
        // memberRepository가 null! → 테스트 불가
        // Spring 컨테이너 없이는 주입 방법이 없음
    }
}

생성자 주입 - 테스트하기 쉽다

@Service
public class MemberService {
    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

// 테스트 - Spring 없이 순수 Java로 가능
class MemberServiceTest {
    @Test
    void test() {
        // Mock 객체나 가짜 객체를 직접 생성해서 주입
        MemberRepository mockRepository = new FakeMemberRepository();
        MemberService service = new MemberService(mockRepository); // 직접 주입 가능

        // 테스트 실행
        Member member = service.findById(1L);
        assertNotNull(member);
    }
}

생성자로 의존성을 받기 때문에 테스트 시 가짜 객체(Mock)를 직접 주입할 수 있습니다.


순환 참조 방지

생성자 주입은 순환 참조 문제를 컴파일 시점에 발견할 수 있습니다.

순환 참조: A → B → A → B → ... (무한 반복)

MemberService → OrderService → MemberService (순환!)
// 필드 주입: 순환 참조가 있어도 애플리케이션이 실행됨 → 런타임 오류
// 생성자 주입: 순환 참조 발생 시 애플리케이션 시작 자체가 실패 → 즉시 발견 가능

생성자 주입은 순환 참조를 런타임이 아닌 시작 시점에 바로 잡아줍니다.


@RequiredArgsConstructor로 더 간결하게

Lombok의 @RequiredArgsConstructor를 사용하면 final 필드에 대한 생성자를 자동으로 만들어줍니다.
@Autowired도 생략 가능해 코드가 훨씬 간결해집니다.

// @RequiredArgsConstructor 사용 전
@Service
public class MemberService {
    private final MemberRepository memberRepository;
    private final MailService mailService;

    @Autowired
    public MemberService(MemberRepository memberRepository, MailService mailService) {
        this.memberRepository = memberRepository;
        this.mailService = mailService;
    }
}

// @RequiredArgsConstructor 사용 후
@Service
@RequiredArgsConstructor // final 필드 생성자 자동 생성
public class MemberService {
    private final MemberRepository memberRepository;
    private final MailService mailService;
    // 생성자 코드 자동 생성됨 → 코드 간결
}

Creator-Flex 프로젝트에서도 @RequiredArgsConstructor를 적극 활용했습니다.


세 가지 주입 방식 비교

항목 필드 주입 수정자 주입 생성자 주입
final 사용 불가 불가 가능
불변성 보장 안 됨 안 됨 보장됨
테스트 용이성 낮음 보통 높음
순환 참조 탐지 런타임 런타임 시작 시점
코드 간결성 간결 보통 Lombok으로 간결 가능
Spring 권장 여부 비권장 선택적 의존성에만 권장

정리

  • 생성자 주입은 @Autowired가 붙은 생성자를 통해 객체 생성 시점에 의존성을 주입받는 방식이다.
  • final 키워드를 사용할 수 있어 한 번 주입된 의존성이 변경되지 않는 불변성이 보장된다.
  • Spring 컨테이너 없이 생성자에 직접 Mock 객체를 주입할 수 있어 테스트가 용이하다.
  • 순환 참조 문제를 런타임이 아닌 애플리케이션 시작 시점에 발견할 수 있다.
  • @RequiredArgsConstructor를 함께 사용하면 생성자 코드 없이도 간결하게 생성자 주입을 구현할 수 있다.