Study/Spring Framework

[Spring] DI(Dependency Injection) 란?

jonghne 2023. 10. 4. 18:05

DI 개념

DI(Dependency Injection)란 애플리케이션에서 객체 간의 의존 관계를 외부에서 주입하는 것을 말한다.

 

의존 관계란 ?
의존 관계란 특정 대상에 의존하여 관계를 맺고 있는 것으로, 의존 대상이 변경되면 의존하고 있는 대상이 영향이 가는 관계이다.
만약 A 클래스에서 B 클래스를 참조하여 사용하고 있다면, A클래스는 B클래스에 의존하고 있다고 한다.

 

 

DI를 사용하면 객체 지향에서 강조하는역할과 책임을 분리하여,유연하고 변경에 용이한 애플리케이션을 만들 수 있다.

 

DI 예시

아래 예시와 같이 Car 클래스와 Engine 인터페이스, Engine 인터페이스의 구현 클래스 두개가 있다고 가정해보자

 

Car 클래스는 Engine 인터페이스의 구현 클래스인 GasolineEngine 인스턴스를 클래스 내부에 직접 생성하여 사용하고 있다

 

그런데 만약 Engine을 DieselEngine 로 변경하려 한다면 Car 클래스의 engine 객체 생성부분을 직접 수정해야 한다. 

public class Car {
    private Engine engine = new GasolineEngine();

    public void start() {
        engine.start();
        System.out.println("Car started");
    }
}

public interface Engine {
    void start();
}

public class GasolineEngine implements Engine {
    public void start() {
        System.out.println("Gasoline engine started");
    }
}

public class DieselEngine implements Engine {
    public void start() {
        System.out.println("Diesel engine started");
    }
}

 

이는 의존관계 대상이 수정됨에 따라 클라이언트의 코드도 수정해야 한다는 점에 있어서 

 

객체 지향 원칙인 단일 책임 원칙(SRP), 개방 폐쇄 원칙(OCP), 의존관계 역전 원칙(DIP)를 위반하고 있다.

 

이러한 문제를 해결하기 위해서는 아래와 같이 Car 클래스에서 Engine 인터페이스 구현체를 생성 하는 것이 아닌, 외부에서 구현 클래스를 정하도록 변경해야 한다. 

 

이렇게 외부에서 객체 간 의존 관계를 설정하여 의존 대상이 변경되어도 클라이언트 코드에는 영향이 가지 않도록 해주는 기술이 DI (Dependency Injection) 이다.

public class Car {
    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.start();
        System.out.println("Car started");
    }
}

public interface Engine {
    void start();
}

public class GasolineEngine implements Engine {
    public void start() {
        System.out.println("Gasoline engine started");
    }
}

public class DieselEngine implements Engine {
    public void start() {
        System.out.println("Diesel engine started");
    }
}

 

DI 주입 방법

DI(Dependency Injection) 주입 방법은 아래와 같이 생성자 주입, 세터 주입, 필드 주입 3가지가 있다. 

 

생성자 주입(Constructor Injection)

객체를 생성할 때 생성자를 통해 의존성을 주입하는 방법으로, 생성자를 통해 의존하는 객체를 외부에서 전달받아 필드에 할당한다.

public class Car {
    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    // ...
}

 

Spring 프레임워크에서는 아래와 같은 이유로 DI 주입 시 생성자 주입 방법을 권장하고 있다. 

  • 객체의 불변성: 생성자를 통해 의존성을 주입하면, 한 번 생성된 객체는 의존성을 변경할 수 없다. 이는 객체의 불변성을 보장하고, 객체의 일관성과 안정성을 유지하는 데 도움이 된다.
  • 의존성을 명시적으로 표현: 생성자 주입은 객체가 필요로 하는 의존성을 생성자 파라미터로 선언함으로써, 코드를 읽는 사람이 객체의 의존성을 쉽게 파악할 수 있다.
  • 의존성 주입의 일관성: 생성자 주입은 객체 생성 시점에 모든 의존성을 받기 때문에, 객체의 생성과 동시에 의존성이 주입되는 일관성을 가진다. 이는 객체의 초기화 과정을 명확하게 관리할 수 있도록 도와준다.
  • 테스트 용이성: 생성자 주입을 사용하면 의존성을 외부에서 주입할 수 있기 때문에, 테스트 시에 의존성을 모의(Mock) 또는 가짜(Fake) 객체로 대체하여 단위 테스트를 수행할 수 있다. 이는 테스트의 격리성과 독립성을 높여준다.

 

세터 주입(Setter Injection)

Setter 메서드를 통해 의존성을 주입하는 방법으로, 외부에서 의존하는 객체를 설정하는 Setter 메서드를 제공해서 객체를 필드에 할당한다.

public class Car {
    private Engine engine;

    // Setter 메서드
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    // ...
}

 

 

필드 주입(Field Injection)

주입할 의존 객체를 필드로 선언하고, @Autowired 어노테이션을 사용하여 Spring Container로 부터 주입받는 방법이다.

 

필드 주입 시에는 주입 받으려는 대상이 Spring Bean으로 등록되어 있어야 한다. 

public class Car {
    @Autowired
    private Engine engine;

    // ...
}