[Spring] DI - Dependency Injection, 의존성 주입과 의존성 주입 방법 세 가지

Euiyeon Park·2025년 2월 19일
0

Spring / SpringBoot

목록 보기
1/4
post-thumbnail

🍀DI(Dependency Injection, 의존성 주입)

  • 객체 간 의존 관계를 외부에서 주입하는 설계 패턴
  • 객체가 직접 다른 객체를 생성하지 않고, 외부에서 주입받도록 하는 것이 핵심
  • 스프링에서는 IoC(Inversion of Control) 개념을 기반으로,
    개발자가 객체의 생명주기를 직접 관리하지 않고 스프링 컨테이너가 관리
  • 즉, 스프링이 필요한 객체를 대신 생성하고 주입해주는 것이 DI

🪄 DI를 사용하는 이유

  • 객체 간 결합도를 낮춰 유지보수성을 높임
  • 테스트가 쉬워지고, Mock 객체 사용이 가능
  • 코드 재사용성이 증가하고 확장이 용이
  • Spring에서 빈(Bean) 관리가 편리

🪄 DI 방식

  1. 생성자 주입(Constructor Injection) - 권장✅
  2. 필드 주입(Field Injection) - 지양❌
  3. Setter 주입(Setter Injection) - 선택⚠️

🤍 1. 생성자 주입

  • 가장 권장되는 DI 방식
  • 의존성을 @Autowired 없이 생성자를 통해 주입
  • 생성자 호출 시점에 1회 호출되는 것을 보장
    • 객체가 생성될 때 의존성을 한 번에 주입받기 때문에,
      의존성 주입 후에 변경이 불가능하다는 점에서 불변성을 확보
  • final키워드를 활용해 불변 객체 유지 가능
  • 순환 참조 문제를 애플리케이션 실행 시점에서 발견 가능
@Service
public class OrderService {

    private final PaymentService paymentService;

    // 생성자를 통해 의존성 주입
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void processOrder() {
        paymentService.pay();
    }
}

🤍 2. 필드 주입

  • @Autowired를 의존성을 주입받는 클래스 필드에 직접 선언하여 의존성 주입
  • 스프링 컨테이너가 없으면 객체를 생성할 수 없음
    • 필드 주입은 스프링이 Reflection API 을 이용해, @Autowired 필드에 의존성 주입
    • 따라서 private 필드에서 의존성 주입이 가능
  • final을 사용할 수 없어 불변성 보장 불가❌
  • 외부에서 필드에 접근이 불가능에 테스트가 어려움
@Service
public class OrderService {

    @Autowired
    private PaymentService paymentService;

    public void processOrder() {
        paymentService.pay();
    }
}

🤍 3. Setter 주입

  • @Autowired를 필드 값을 변경하는 Setter 메서드에 붙여서 의존성을 주입
  • 의존성을 동적으로 변경할 필요가 있을 때 사용 가능
    • 주입받는 객체가 변경될 가능성이 있는 경우 사용
  • Setter 메서드를 public으로 열어두는 경우, 외부 변경이 가능
    • 불변성을 유지하기 어려움
@Service
public class OrderService {

    private PaymentService paymentService;

    @Autowired
    public void setPaymentService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void processOrder() {
        paymentService.pay();
    }
}

🍀 생성자 주입을 권장하는 이유

왜 생성자 주입이 불변성을 확보할까?

1. 불변성 확보 가능

  • 생성자 주입 방식에서 의존성은 객체 생성 시 한번만 주입되고
    이후 변경될 수 없어 불변성을 보장
    할 수 있다. - ✨생성자는 객체 생성 시 1회만 호출된다
  • final 을 사용해 필드가 반드시 할당되도록 강제 가능하다.

2. 의존성 누락 방지

  • 생성자 주입을 사용하면 필수 의존성이 반드시 주입되지 않은 경우
    객체가 생성될 수 없다.
  • 컴파일 타임 또는 애플리케이션 실행 시점에 의존성 누릭을 감지할 수 있다.

💡Setter 주입에서는 누락이 가능해 NullPointerException 발생 위험

3. 순환 참조 방지

  • 스프링에서는 순환 참조(두 개 이상의 Bean이 서로를 의존하는 문제)가 발생하면
    오류를 발생시키는데, 생성자 주입을 사용하면 순환 참조 문제를
    애플리케이션 실행 전에 감지 가능하다.

💡 Setter 주입과 필드 주입은 빈이 생성된 이후에 참조를 하기 때문에
애플리케이션이 아무런 오류나 경고없이 실행된다.

4. 테스트 용이성

  • 생성자 주입을 사용하면 스프링 없이도 객체를 직접 생성해 단위 테스트가 가능하다.
  • Mock 객체를 활용한 테스트가 쉽다.
@Test
void testOrderService() {
    PaymentService mockPaymentService = mock(PaymentService.class);
    OrderService orderService = new OrderService(mockPaymentService);

    orderService.processOrder();

    verify(mockPaymentService).pay();
}

profile
"개발자는 해결사이자 발견자이다✨" - Michael C. Feathers

0개의 댓글