CS/프레임워크

DI(Dependency Injection) 의존성 주입

glorypang 2025. 9. 8. 18:09
728x90
반응형
SMALL

의존 관계란 무엇인가?

Java는 객체지향 언어입니다. 따라서 객체들 간의 관계를 적절히 맺어주는 것이 매우 중요합니다.

"의존 관계"라는 것은 거창한 개념이 아닙니다.

A 인스턴스가 B 인스턴스의 메서드를 호출하고 있다면, A는 B에 의존하는 관계입니다.
A가 B의 기능을 가져다 사용하고 있으니까요.

class A {
    public void methodOfA() {
        B b = new B();  // A가 B를 직접 생성
        b.example();    // B의 메서드를 사용 → A가 B에 의존
    }
}

class B {
    public void example() {
        System.out.println("B의 기능 실행");
    }
}

기존 방식의 문제점

위 코드에서 A와 B의 의존 관계는 개발자가 직접 만들었습니다.

`new` 키워드로 직접 인스턴스를 생성했기 때문입니다.

하지만 이 방식에는 심각한 문제가 있습니다.

 

상황: A가 B 대신 새로운 클래스 C를 사용하고 싶다면?

class A {
    public void methodOfA() {
        // B b = new B();     // 기존 코드 삭제
        // b.example();
        
        C c = new C();        // 새로운 코드 추가
        c.example();
    }
}

class C {
    public void example() {
        System.out.println("C의 기능 실행");
    }
}

문제: 만약 B를 사용하던 클래스가 A 하나가 아니라 수십, 수백 개라면?

모든 클래스의 코드를 일일이 수정해야 합니다.

DI 방식으로 해결하기

스프링의 DI를 사용하면 이 문제를 해결할 수 있습니다.

핵심 아이디어

  1. 직접 객체를 만들지 않는다.
  2. 외부에서 만들어서 넣어준다(주입)

1단계: 추상화 (인터페이스 도입)

  • A가 직접 B를 알지 않도록,
  • A가 필요로 하는 기능을 인터페이스로 정의

2단계: 구현체들이 인터페이스를 구현

// A가 사용하는 메서드를 인터페이스로 추상화
interface I {
    void example();
}
class B implements I {
    public void example() {
        System.out.println("B의 기능 실행");
    }
}

class C implements I {
    public void example() {
        System.out.println("C의 기능 실행");
    }
}

3단계: 외부 주입 방식으로 변경

  • A는 인터페이스만 의존
  • 어떤 구현체를 쓸지는 ‘외부’가 결정
class A {
    private I i;  // 인터페이스 타입 필드

    // 생성자를 통해 외부에서 구현체를 받음
    public A(I i) {
        this.i = i;
    }

    public void methodOfA() {
        i.example();  // 어떤 구현체든 상관없이 호출
    }
}

핵심 변화

Before (기존 방식):

  • A가 직접 B를 생성: `B b = new B()`
  • A가 B에 강하게 결합됨

After (DI 방식):

  • A는 자신이 사용할 객체를 알지 못함
  • A는 단지 `i.example()` 메서드가 있다는 것만 알고 있음
  • 외부에서 A에게 필요한 객체를 주입해줌

누가 의존성을 주입해주는가?

그렇다면 누군가가 A에게 필요한 객체(B든 C든)를 결정하고 생성해서 전달해주어야 합니다.

그 누군가가 바로 스프링입니다!

// 개발자는 설정만 해둡니다
@Configuration
public class AppConfig {
    @Bean
    public I getImplementation() {
        return new C();  // C를 사용하겠다고 설정
    }
    
    @Bean
    public A getA(I i) {
        return new A(i);  // 스프링이 자동으로 C를 주입해줌
    }
}

동작 과정:

  1. 개발자가 설정 파일에 "A는 C를 사용하겠다"고 설정
  2. 애플리케이션 실행 시 스프링이 설정을 해석
  3. 스프링이 C 객체를 생성
  4. 스프링이 A 생성자에 C를 전달하여 A 생성
  5. A는 C가 주입된 상태로 사용 가능

DI의 장점

  1. 유연성: B에서 C로 바꾸려면 설정 파일만 수정하면 됨
  2. 재사용성: A 클래스 코드 변경 없이 다양한 구현체 사용 가능
  3. 테스트 용이성: Mock 객체를 쉽게 주입할 수 있음
  4. 결합도 감소: A는 구체 클래스(B, C)를 몰라도 됨
728x90
반응형
LIST