어제 오늘 내일

[Spring Boot] "객체 관리는 스프링에게 맡기세요!" 의존성 주입(DI) 완벽 가이드 본문

IT/SpringBoot

[Spring Boot] "객체 관리는 스프링에게 맡기세요!" 의존성 주입(DI) 완벽 가이드

hi.anna 2026. 3. 8. 07:35

 

Spring Boot를 공부하다 보면 "DI(Dependency Injection, 의존성 주입)"라는 단어를 정말 많이 듣게 됩니다.

면접 단골 질문이기도 하고, 좋은 객체지향 설계를 위해 반드시 이해해야 하는 개념이죠.

오늘은 DI가 도대체 무엇인지, 그리고 왜 필드 주입보다 생성자 주입을 권장하는지 명쾌하게 정리해 드리겠습니다.

 


 

1. 의존성 주입(DI)이 뭔가요?

요리를 한다고 상상해 봅시다.

  • DI가 없는 경우: 요리사가 요리할 때마다 직접 농장에 가서 재료를 캐오고, 칼을 대장간에서 만들어옵니다. (객체가 의존 객체를 직접 생성 new)
  • DI가 있는 경우: 요리사는 요리에만 집중하고, 누군가가 손질된 재료와 좋은 칼을 주방에 딱 놔줍니다. (외부에서 의존 객체를 주입)

프로그래밍에서 DI(Dependency Injection)란, 객체가 자신이 사용할 의존성(다른 객체)을 직접 생성하지 않고, 외부(스프링 컨테이너)로부터 주입받아 사용하는 디자인 패턴입니다.

이로 인해 개발자는 객체 간의 결합도를 낮추고 유연한 코드를 짤 수 있게 됩니다. 이것이 바로 IoC(Inversion of Control, 제어의 역전)의 핵심입니다.

 


 

2. 스프링에게 "이거 관리해 줘"라고 말하는 법 (Bean 등록)

DI를 받으려면 먼저 스프링 컨테이너에 "이 객체는 네가 관리해!"라고 등록해야 합니다. 이렇게 등록된 객체를 빈(Bean)이라고 부릅니다.

주로 다음과 같은 어노테이션을 클래스 위에 붙여 사용합니다.

  • @Component: 일반적인 컴포넌트
  • @Service: 비즈니스 로직을 담당하는 서비스
  • @Repository: DB 접근을 담당하는 리포지토리
  • @Controller: 웹 요청을 처리하는 컨트롤러

 


 

3. 의존성을 주입받는 3가지 방법

빈으로 등록된 객체를 가져다 쓰는(주입받는) 방법은 크게 세 가지가 있습니다.

① 필드 주입 (Field Injection)

가장 코드가 짧고 편해서 과거에 많이 쓰였습니다.

@Service
public class UserService {

    @Autowired // 필드 위에 바로 붙임
    private UserRepository userRepository;
}
  • 단점: 외부에서 변경이 불가능하여 테스트하기 어렵고, 프레임워크(Spring)에 너무 의존적입니다. (비추천)

② 세터 주입 (Setter Injection)

Setter 메서드를 통해 주입받습니다.

@Service
public class UserService {

    private UserRepository userRepository;

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}
  • 특징: 선택적인 의존성이나 변경 가능성이 있는 의존성에 사용합니다. 하지만 실무에서는 거의 쓸 일이 없습니다.

③ 생성자 주입 (Constructor Injection) ★강력 추천★

생성자를 통해 주입받습니다. (Spring 4.3부터는 생성자가 하나면 @Autowired 생략 가능)

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

 

 


 

4. 왜 "생성자 주입"을 써야 할까요?

IntelliJ 같은 IDE에서도 필드 주입을 쓰면 경고를 띄우며 생성자 주입을 권장합니다. 그 이유는 명확합니다.

  1. 불변성(Immutability) 보장: final 키워드를 사용할 수 있습니다. 한 번 주입되면 절대 변하지 않으므로 안전합니다.
  2. 테스트 용이성: 순수한 자바 코드로 단위 테스트를 작성할 때, 생성자로 가짜 객체(Mock)를 주입하기 쉽습니다.
  3. NPE(Null Pointer Exception) 방지: 객체 생성 시점에 의존성이 없으면 아예 컴파일 에러나 실행 에러가 발생하므로, Null 상태로 돌아가는 것을 원천 봉쇄합니다.
  4. 순환 참조 감지: A가 B를 참조하고 B가 A를 참조하는 악순환을 앱 구동 시점에 바로 잡아낼 수 있습니다.

 


 

💡 꿀팁: Lombok으로 코드 다이어트하기

생성자 주입이 좋은 건 알겠는데, 코드가 너무 길어진다고요? Lombok 라이브러리의 @RequiredArgsConstructor를 쓰면 마법처럼 해결됩니다.

@Service
@RequiredArgsConstructor // final이 붙은 필드의 생성자를 자동 생성!
public class UserService {

    private final UserRepository userRepository;
    private final MemberRepository memberRepository;

    // 생성자 코드를 짤 필요가 없습니다.
}

실무에서는 99% 이 방식을 사용합니다. 깔끔하고 안전하죠!

 


 

마치며

오늘의 결론입니다.

  1. DI는 객체를 직접 만들지 않고 외부에서 받는 것이다.
  2. @Autowired 필드 주입은 이제 그만!
  3. private final + 생성자 주입 (with Lombok) 패턴을 사용하자.

이 기본기만 탄탄해도 Spring Boot 애플리케이션을 훨씬 더 안정적으로 설계하실 수 있습니다.

다음 포스팅에서는 Spring Bean의 생명주기(Lifecycle)와 Scope에 대해 다뤄보겠습니다. 

 

 

반응형
Comments