의존성 주입(DI)
스프링 공식 문서에 따르면, 의존성 주입은 객체가 자신의 의존성(함께 작동하는 다른 객체들)을 직접 생성하지 않고, 외부로부터 주입받는 디자인 패턴이다.
쉽게 설명하면, 필요한 물건을 내가 직접 만들지 않고 외부에서 가져다 쓰는 방식이다.
코드로 보는 의존성 주입의 필요성
의존성 주입을 사용하지 않은 경우
아래의 코드는 자동차 회사가 자동차 조립과 엔진을 같이 만들고 있다고 생각하면 된다.
만약 엔진을 전기 엔진으로 바꾸고 싶다면 자동차 공장의 생산라인을 수정해야 한다. 즉, 엔진에 변동사항이 생긴다면 자동차 생산 전체에 영향이 간다.
public class Car {
private GasolineEngine gasolineEngine;
public Car() {
this.gasolineEngine = new GasolineEngine(); // 자동차 회사가 직접 엔진을 생산
}
}
위와 같은 코드는 아래와 같은 문제점을 가진다.
1. 강한 결합 (Tight Coupling)
- Car 클래스가 GasolineEngine이라는 구체 클래스에 직접 의존
- 다른 종류의 엔진(ElectricEngine, HydrogenEngine 등)으로 교체가 어려움
2. 단일 책임 원칙(SRP) 위반
- Car는 자동차 기능뿐만 아니라 엔진 생성까지 책임지게 됨
- "자동차 회사가 엔진 공장까지 직접 운영하는 것"과 같은 상황
의존성 주입을 사용한 경우
아래의 코드는 Engine이라는 인터페이스를 생성하고, 이를 구현하는 GasolineEngine과 ElectricEngine을 만든 예시이다.
이는 마치 자동차 회사가 엔진의 표준 스펙(인터페이스)만 정의하고, 실제 엔진은 외부 전문 업체들(구현체들)이 만드는 것과 같다.
public interface Engine {
void start();
void stop();
}
public class GasolineEngine implements Engine {
@Override
public void start() {
System.out.println("가솔린 엔진 시동");
}
@Override
public void stop() {
System.out.println("가솔린 엔진 정지");
}
}
자동차 회사는 이제 외부에서 엔진을 주입받아 사용할 수 있다.
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine= engine; // 외부에서 주입
}
// 가솔린 엔진 사용
public void startGasolineEngine() {
engine.start();
}
}
인터페이스의 구현체가 여러 개일 경우
인터페이스의 구현체가 여러 개 있을 때는 상황에 따라 네 가지 방식으로 처리할 수 있다.
1. 특정 구현체만 선택해서 사용해야 하는 경우
- @Primary: 여러 구현체 중 기본으로 주입될 구현체 지정
- @Qualifier: 특정 구현체를 지정해서 주입받을 때 사용
2. 모든 구현체를 한번에 사용해야 하는 경우
- List: 모든 구현체를 리스트로 주입받아 사용
- Map: 빈 이름과 구현체를 쌍으로 주입받아 사용
아래와 같이 Engine 인터페이스와 이를 구현한 가솔린 엔진, 전기 엔진 클래스가 있다.
public interface Engine {
void start();
void stop();
}
// 가솔린 엔진
public class GasolineEngine implements Engine {
@Override
public void start() {
System.out.println("가솔린 엔진 시동");
}
@Override
public void stop() {
System.out.println("가솔린 엔진 정지");
}
}
// 전기 엔진
public class ElectricEngine implements Engine {
@Override
public void start() {
System.out.println("전기 엔진 시동");
}
@Override
public void stop() {
System.out.println("전기 엔진 정지");
}
}
특정 구현체만 선택해서 사용하는 경우
특정 구현체를 선택해야 하는 경우에는 @Primary나 @Qualifier 어노테이션을 사용한다.
@Primary 사용
@Primary는 여러 구현체 중 우선적으로 주입될 기본 구현체를 지정하는데 사용된다.
@Component
@Primary
public class GasolineEngine implements Engine {
@Override
public void start() {
System.out.println("가솔린 엔진 시동");
}
}
@Service
public class Car {
private final Engine engine; // GasolineEngine이 주입
public Car(Engine engine) {
this.engine = engine;
}
}
@Qualifier 사용
@Qualifier는 여러 구현체 중 특정 구현체를 지정해서 주입받고 싶을 때 사용한다.
@Component
@Qualifier("gasolineEngine")
public class GasolineEngine implements Engine {
@Override
public void start() {
System.out.println("가솔린 엔진 시동");
}
}
@Component
@Qualifier("electricEngine")
public class ElectricEngine implements Engine {
@Override
public void start() {
System.out.println("전기 엔진 시동");
}
}
@Service
public class Car {
private final Engine engine;
public Car(@Qualifier("electricEngine") Engine engine) { // 전기 엔진을 주입받음
this.engine = engine;
}
}
모든 구현체를 사용해야 하는 경우
모든 구현체를 사용해야 하는 경우에는 List나 Map을 통해 여러 구현체를 한번에 주입받을 수 있다.
@Service
public class Car {
private final Map<String, Engine> engineMap;
private final List<Engine> engines;
public Car(Map<String, Engine> engineMap, List<Engine> engines) {
this.engineMap = engineMap;
this.engines = engines;
}
// 전기 엔진만 사용
public void startElectricEngine() {
Engine electricEngine = engineMap.get("electricEngine");
electricEngine.start();
}
// 모든 엔진 시동
public void startAllEngines() {
for (Engine engine : engines) {
engine.start();
}
}
}
의존성 주입(DI)은 객체가 필요한 의존성을 외부에서 주입받는 방식으로, 객체 간의 결합도를 낮추고 유연한 코드 작성을 가능하게 합니다. 주입 방식에는 생성자 주입, 필드 주입, 수정자 주입이 있는데, 스프링에서는 불변성과 필수 의존성을 보장하는 생성자 주입을 권장하고 있다.
참고
Dependency Injection :: Spring Framework
Dependency Injection :: Spring Framework
Constructor-based DI is accomplished by the container invoking a constructor with a number of arguments, each representing a dependency. Calling a static factory method with specific arguments to construct the bean is nearly equivalent, and this discussion
docs.spring.io
'Spring' 카테고리의 다른 글
JPA란? (3) | 2024.09.01 |
---|---|
의존성 주입 방법 (0) | 2024.08.25 |
Spring boot 3 버전 이상 swagger 사용법 (0) | 2024.08.24 |
IoC란? (0) | 2024.08.20 |
Controller와 RestController (0) | 2024.08.17 |