의존성 주입 방식 3가지 비교 feat. DI

Chaaehyun·2025년 4월 25일

스프링 스터디

목록 보기
7/10
post-thumbnail

정의

Spring에서 의존성 주입은 객체 간 결합도를 낮추고 테스트와 유지보수를 쉽게 하기 위한 핵심 원칙입니다.
대표적으로 생성자 주입, 필드 주입, 세터 주입이 있으며, 각 방식은 코드 구조, 장단점이 분명히 다릅니다.


✅ 1. 생성자 주입 (Constructor Injection) - 가장 권장

@Component
public class RamenCook {
    private final Ingredient ingredient;
    private final CookingTool tool;

    @Autowired
    public RamenCook(Ingredient ingredient, CookingTool tool) {
        this.ingredient = ingredient;
        this.tool = tool;
    }

    public void cook() {
        ingredient.prepare();
        tool.use();
    }
}
  • final 필드 사용 가능 → 불변성 보장

  • 컴파일 타임에 주입이 안될 시, 감지 할 수 있다.

@Test
void testCook() {
    Ingredient mockIngredient = mock(Ingredient.class);
    CookingTool mockTool = mock(CookingTool.class);

    RamenCook ramenCook = new RamenCook(mockIngredient, mockTool); // ✅ 직접 주입
    ramenCook.cook(); // ✅ 정상 동작
}
  • 단위 테스트 시 생성자를 통해 Mock 객체 주입 쉬움

❌ 2. 필드 주입 (Field Injection) - 비추천

@Component
public class RamenCook {

    @Autowired
    private Ingredient ingredient;

    @Autowired
    private CookingTool tool;
}
  • 가장 간단하지만, 테스트 어려움
public class RamenCookTest {

    @Test
    void testCook() {
        RamenCook ramenCook = new RamenCook(); // 직접 생성

        ramenCook.cook(); // ❌ NullPointerException 발생!
    }
}
  • Spring 컨테이너 없이는 동작 불가

    예시 Spring이 없기 때문에:

    1. @Autowired는 Spring IoC 컨테이너가 작동할 때만 의미가 있음

      => 위 예제에서는 new RamenCook()으로 직접 객체를 생성했기 때문에 Spring은 관여하지 않음

    2. ingredient, tool은 여전히 null 상태

    3. 결과적으로 cook() 메서드 실행 시 NullPointerException 발생!

      => 결국 NullPointerException 발생 가능


⚖️ 3. 세터 주입 (Setter Injection) - 조건부 사용

@Component
public class RamenCook {
    private Ingredient ingredient;
    private CookingTool tool;

    @Autowired
    public void setIngredient(Ingredient ingredient) {
        this.ingredient = ingredient;
    }

    @Autowired
    public void setTool(CookingTool tool) {
        this.tool = tool;
    }

    public String cook() {
        return ingredient.prepare() + " + " + tool.use();
    }
}
  • 선택적 의존성 주입에 적합
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

public class RamenCookTest {

    @Test
    void testCook_withSetterInjection() {
        // 1. Mock 객체 생성
        Ingredient mockIngredient = mock(Ingredient.class);
        CookingTool mockTool = mock(CookingTool.class);

        // 2. Mock 동작 정의
        when(mockIngredient.prepare()).thenReturn("면 준비 완료");
        when(mockTool.use()).thenReturn("냄비 가열 완료");

        // 3. 테스트 대상 객체 생성
        RamenCook ramenCook = new RamenCook();

        // 4. Setter로 Mock 객체 주입
        ramenCook.setIngredient(mockIngredient);
        ramenCook.setTool(mockTool);

        // 5. 테스트 실행 및 결과 검증
        String result = ramenCook.cook();
        assertEquals("면 준비 완료 + 냄비 가열 완료", result);
    }
}
  • 초기화 이후 변경 가능 (유연하지만 위험성 존재)

  • 객체 생성 시점에는 완전한 상태가 아닐 수 있음

    Setter 주입 방식에서 객체를 만들자마자 사용할 경우, 아직 의존성이 주입되지 않았을 수도 있어서 문제가 생길 수 있다는 뜻
    즉, 객체가 생성됐다고 해서 바로 사용할 수 있는 상태는 아닐 수도 있다는 의미

  • But, 유연성 높음!

    👉 사용 예: 의존성을 나중에 바꿔치기 가능 (Setter 주입의 유연성)

    RamenCook cook = new RamenCook();
    
    cook.setIngredient(new Ingredient("라면"));      // 처음엔 라면 재료
    cook.setTool(new CookingTool("냄비"));           // 처음엔 냄비 사용
    cook.cook();  // 🍜 라면 요리
    
    cook.setIngredient(new Ingredient("우동"));      // 나중에 우동 재료로 변경
    cook.setTool(new CookingTool("전기포트"));       // 도구도 전기포트로 교체
    cook.cook();  // 🍜 우동 요리
    • 객체를 만든 다음에도 필요에 따라 설정을 바꿀 수 있습니다.

    • 어떤 의존성은 "필수는 아니고, 있으면 좋음" => 선택적 주입에 적합합니다.

    • 설정성 객체, 조건부 기능 컴포넌트 등에 많이 활용됩니다.


🔁 결론

구분생성자 주입필드 주입세터 주입
코드 구조생성자에서 주입필드에 직접 주입Setter 메서드를 통해 주입
불변성✅ final 사용 가능❌ 불가❌ 불가
테스트 용이성✅ 매우 좋음❌ 어려움 (리플렉션 필요)⚠️ 중간 정도
필수 의존성 누락컴파일 타임에 감지 가능런타임까지 감지 어려움주입 누락 시 NPE 가능성 있음
유연성제한적 (불변 구조, final)낮음높음 (선택적 의존성에 적합)
주로 사용되는 곳일반적인 비즈니스 로직예외적 상황, 프레임워크 내부설정용 Bean, 조건부 구성 요소
profile
세상을 사랑하는 컴퓨터학부생입니다 :>

0개의 댓글