토비의 스프링(p.114
)에서는 다음과 같은 세 가지의 조건을 만족해야 DI
라고 표현하고 있습니다.
이 조건을 토대로, DI
를 정의하면 다음과 같을 것입니다.
- 특정
빈
이 동작하기 위해 필요한의존 관계
를 내부적으로 직접 생성하지 않음컴파일 시점
에서는인터페이스
로 의존하고 있음런타임 시점
에는필요한 구현체
들을 컨테이너나 팩토리 같은 제 3의 존재가 주입
빈
을 생성하기 위한 의존 관계를 내부가 아닌 외부에서 주입받게 되므로, 스프링에서는 이를 제어의 역전(Inversion of Control, IoC
)이라고도 표현합니다.
DI
를 사용하면 다음과 같은 이점을 얻을 수 있습니다.
SRP
)를 지킬 수 있습니다.DI
를 받지 않고 내부에서 필요한 객체를 직접 생성하는 경우, 해당 클래스의 비즈니스 로직에 대한 책임 + 필요한 객체를 생성하는 책임
모두를 가지고 있다고 판단할 수 있습니다.빈
)을 손쉽게 변경할 수 있습니다.빈
)를 주입해주기 때문에, 컴파일 시점의 의존 관계를 그대로 유지한 채로, 테스트의 런타임 시점의 의존 관계를 변경할 수 있습니다.스프링
에서는 @Autowired
를 통해 의존성을 주입합니다.
이렇게 의존성을 주입하는 방법은 다음과 같이 4가지가 존재합니다.
setter
주입public class MyBean {
private final A a;
@Autowired
public MyBean(A a) {
this.a = a;
}
}
fianl
키워드를 추가할 수 있습니다.null
이 되지 않도록 보장할 수 있습니다.public class MyBean {
private A a;
@Autowired
public void setA(A a) {
this.a = a;
}
}
final
키워드를 추가할 수 없습니다.null
이 될 수 있습니다.public class MyBean {
@Autowired
private A a;
}
final
키워드를 추가할 수 없습니다.public class MyBean {
private A a;
private B b;
@Autowired
public void init(A a, B b) {
this.a = a;
}
}
Bean
을 받아 주입할 수 있습니다.final
키워드를 추가할 수 없습니다.다음과 같은 기준으로 사용할 수 있을 것이라 생각합니다.
Production 환경
Test 환경
DI
방식은 사용하지 않음생성자 주입을 사용해야 하는 이유 두 가지는 다음과 같습니다.
final
키워드 추가(불변 및 null
을 허용하지 않음)이는 다른 방식에 비해 월등히 뛰어난 방식이기 때문에, 다른 방식을 사용할 이유가 없다고 볼 수 있습니다.
다만 Test 환경
에서는 필드 주입을 사용할 것이라 명시했는데, 이는 편의성 때문입니다.
A
가 B
를 사용하는 경우, B
가 변경되면 A
가 영향을 받게 됩니다.DI
가 아닌걸까?DI
의 목적을 생각해봐야 할 것 같습니다.DI
는 결국, A
가 B
를 사용할 때, B
의 변경에 영향을 최소화하기 위해 사용하는 것입니다.B
를 인터페이스로 추상화하고, B
의 구현체를 제 3의 존재가 주입해주는 것입니다.B
를 추상화하지 않고 B의 구현체
를 그대로 사용한다면, 외부에서 주입받는다고 하더라도 A
는 B
에게 계속 영향을 받게 됩니다.DI
를 수행하는 이유가 없어지므로, 엄밀히 따지자면 DI
가 아니라고 볼 수 있습니다.빈
으로 등록할 모든 클래스에 대해 추상화를 적용하는 것은 비용이 커질 수 있기 때문에, 환경에 따라 적절히 선택해야 할 것입니다.IoC
? DI
?IoC
는 객체의 생성과 구성에 대한 제어 권한을 객체 자체에서 외부로 이동하는 디자인 패턴입니다.외부
가 컨테이너인지, 팩토리인지 상관 없이 이를 모두 IoC
라고 볼 수 있습니다.토비의 스프링
에서 DI
조건 중 하나로 런타임 시점에는 필요한 구현체들을 컨테이너나 팩토리 같은 제 3의 존재가 주입
이 있는 것입니다.DI
는 이러한 IoC
를 구현하기 위한 방법 중 하나입니다.DI
는 주로 스프링에서 많이 표현되기 때문에 컨테이너
, 빈
이라는 단어를 통해 설명이 되었다고 볼 수 있습니다.인터페이스를 사용하지 않으면 DI가 아닌걸까?
와 연관이 있는 항목으로 보입니다.A
가 B
를 사용할 때, B
의 변경에 A
가 영향을 받으므로 A
와 B
의 결합도가 강한 상태입니다.DI
와 같은 IoC
없이 A
가 B
를 직접 생성해주는 경우, A
에서 필요한 비즈니스 로직과는 별개로 A
의 로직을 수행하기 위해 필요한 B
를 생성해주는 과정까지 책임을 지니고 있습니다.A의 비즈니스 로직 책임 + B를 생성해주는 책임
A
의 상태는 B
를 생성해주는 책임으로 인해 결합도가 높고(B
의 변경에 영향을 받기 쉬워짐), 응집도가 낮습니다.(A의 비즈니스 로직
뿐만 아니라 B의 생성 책임
까지 지니고 있음)DI
의 장점 중의 하나로 결합도를 낮출 수 있으며, 이 결합도를 낮추는 과정이 B의 생성 책임
을 외부에게 위임하는 것이므로 응집도를 높일 수 있게 되는 것입니다.null
이 되지 않을까? 다른 주입 방법은 null
일 수 있다는 것일까?DI
가 이루어지기 때문에 null
이 되지 않습니다.final
키워드를 붙일 수 있는 이유기도 합니다.일반 메소드 + setter
)는 객체를 생성한 이후 의존 관계가 주입됩니다.null
일 수 있습니다.setter
가 없는 상태에서 필드 주입을 사용하게 되면, 스프링의 DI Container
에 의해서만 의존관계 주입이 가능합니다.