의존성 주입(Dependency Injection)은 객체 간의 의존성을 외부에서 주입받아 결합도를 낮추고 코드의 유연성과 테스트 가능성을 높이는 소프트웨어 설계 패턴이다, 스프링에서 사용하는 의존성 주입 방식에는 필드 주입, 생성자 주입, Setter 주입 세 가지를 정리해보려 한다.

 

 

 


 

필드주입(Field Injection)

클래스의 필드에 @Autowired를 직접 사용하여 의존성을 주입한다.

 

장점

  1. 간결한 코드로 구현가능.
  2. 생성자나 Setter 없이 의존성을 선언 가능

단점

  1. private 필드 접근의 제한으로 *테스트가 어렵다.
  2. 객체초기화 시점에 의존성을 강제하지 않아 null point exception 발생하여 예외가 발생할 있다.
  3. 클래스의 의존성을 명시적으로 표현하지 않아 코드 가독성이 떨어진다.

*필드주입의 테스트가 어려운 이유

클래스의 생성자나 메서드 시그니처에 의존성이 드러나지 않으므로 어떤 의존성이 필요한지 파악하기 힘들고 의존성을 명시적으로 주입할 수 없다.

예시)

@SpringBootTest
class MyServiceTest {
    @Autowired
    private MyService myService;  // 필드 주입
}

 

MyService에 어떤 의존성이 주입되는지 명확히 확인하기 어렵습니다. 생성자나 세터 주입 방식으로 했다면, @Autowired와 함께 실제 어떤 값이 주입될지 더 명확하게 제어할 수 있습니다.

 

 

 

생성자 주입(Constructior Injection)

의존성을 생성자를 통해 주입받는다. 스프링에서는 생성자가 하나만 있을 경우 @Autowired를 생략가능

 

장점

  1. 필드를 final로 선언할 수 있어 불변성을 보장
  2. 클래스의 의존성이 명확히 드러난다
  3. 테스트에서 의존성을 쉽게 Mock로 교체 가능
    *실제 객체를 만들기엔 비용과 시간이 많이 들거나 의존성이 길게 걸쳐져 있어 제대로 구현하기 어려울 경우, 가짜 객체를 만들어 사용하는데 이것을 Mock이라 한다.
  4. 런타임에서 *순환참조를 감지해 오류를 방지

단점

  1. 의존성이 많아질 경우 생성자 매개변수가 많아질 수 있다.

*순환참조

두 개 이상의 빈(Bean)이 서로를 참조하는 상황으로 인해 스프링이 빈을 초기화하기 못하는 문제를 말한다.

순환참조 예시 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AService {

    private final BService bService;

    @Autowired
    public AService(BService bService) {
        this.bService = bService;
    }

    public void print() {
        System.out.println("AService");
    }
}

@Service
public class BService {

    private final AService aService;

    @Autowired
    public BService(AService aService) {
        this.aService = aService;
    }

    public void print() {
        System.out.println("BService");
    }
}

설명

  1. AService 클래스는 BService를 의존하고 있습니다.
  2. BService 클래스는 AService를 의존하고 있습니다.

이렇게 되면 Spring은 AService와 BService를 생성하려고 할 때, 각각의 객체를 생성하는 과정에서 서로가 서로를 참조하므로 무한 루프에 빠지게 됩니다.

 

순환참조의 해결방안

  1. @Lazy 어노테이션 사용하면 빈 초기화를 지연시켜 순환참조 문제를 해결할 수 있다.
  2. 순환 참조의 한쪽을 Setter  주입(Setter Inject)  방식으로 변경하여 해결
  3. 순환 참조 자체를 제거하는 설계변경 가장 바람직하다.

*스프링 부트 2.6이상에서는 순환참조금지가 기본설정으로 순환참조를 허용하려면 설정을 변경해야 한다.

 

Setter 주입(Setter Injection)

Setter 메서드에 @Autowired를 사용하여 의존성을 주입한다.

 

장점

  1. 의존성을 선택적으로 설정가능
  2. 유연한 초기화가 필요한 경우 유용

단점

  1. 필수 의존성이 누락될 가능성이 있어 객체의 *불완전 상태가 발생할 수 있다.
  2. Setter가 외부에서 호출되어 의존성이 변경될 위험이 있다.

 

*Setter 주입의 불완전상태 발생이유

  1. 객체 생성 이후 스프링 컨테이너가 setter 메서드를 호출할 때 의존성이 주입된다. 주입이 완료되기 전 객체가 사용되면 의존성이 없는 상태로 동작할 수 있다.
  2. 주입순서를 명시적으로 제어하기 어렵고 이로 인해 객체가 완전한 상태로 초기화되지 않을 가능성이 있다.
  3. 주입 대상 필드가 초기화 전에 호출되면 null point exception이 발생할 수 있다.

 

 

 

 

 

 

[결과]

필드주입은 간단한 구현에 사용하지만 테스트가 private접근제한이나 객체 초기화 시점의 의존성 강제하지 않아 테스트가 어려울 수 있다.

생성자 주입은 final로 선언이 가능해 불변성이 보장되고 클래스의 의존성이 명확히 드러난다. 하지만 의존성이 많아지면 생성자 매개변수가 많아질 수 있다.

Setter 주입은 유연한 초기화, 의존성을  선택적으로 설정 가능하나 필수 의존성이 누락될 가능성이 있어 객체의 불완전한 상태가 발생할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

+ Recent posts