외부에서 두 객체간의 의존 관계를 결정해주는 디자인 패턴이다.
인터페이스를 사이에 둬서 클래스 레벨에서는 의존 관계가 고정되지 않도록 하고, 런타임시 관계를 동적으로 주입하여 유연성을 확보하고 결합도를 낮출 수 있게 해준다.
피자를 만들어 파는 Store가 있다고 가정해보자.
public class Store {
private final Pizza pizza;
public Store() {
this.pizza = new Pizza();
}
}
위 예제의 클래스는 아래와 같은 문제점을 가지고 있다.
Store에 필요한 Pizza를 스스로 생성한다.Pizza가 아닌 다른 음식을 팔고자 한다면 Store의 생성자에 변경이 필요하다.Store가 제품에 대한 구현 클래스(Pizza)를 알지 못하더라도 인터페이스 타입으로 사용할 수 있다.Store가 어떤 제품을 팔지에 대한 관심사의 분리가 되지 않았다고 볼수있다.위와 같은 문제를 해결하기 위해서는 다형성이 필요하다.
여러 제품을 하나로 표현하기위해 Product 인터페이스를 만든다.
그리고 Pizza에서 Product를 구현하도록 하자.
public interface Product {
}
public class Pizza implements Product {
}
이제 Store와 Pizza의 강하게 결합되어 있는 부분을 제거해보자.
아래와 같이 Store 외부에서 판매할 상품을 주입하자.
그래야 Store에서 구체 클래스(Pizza)에 의존하지 않게 된다.
public class Store {
private final Product product;
public Store(Product product) {
this.product = product;
}
}
여기서 Spring이 DI Container를 필요로 하는 이유를 알 수 있는데, Store에서 Product를 주입받기 위해서 애플리케이션 실행 시점에 필요한 객체를 생성해야 하며, 의존성이 있는 두 객체를 연결시켜 주기 위해 한 객체(Product)를 다른 객체(Store)로 주입 시켜 줘야 한다.
public class Example {
public static void main(String[] args) {
Product product = new Pizza();
// Store를 만들기 위해선, 의존 관계인 Product를 외부에서 주입시켜줘야 한다.
Store store = new Store(product);
}
}
Spring에서는 아래와 같이 BeanFactory가 동적으로 의존 관계를 연결시켜주는 역할을 한다.
public class BeanFactory {
public Store store() {
Product product = new Pizza();
// 의존성이 있는 Product를 외부에서 주입시켜줘야 한다.
Store store = new Store(product);
return store;
}
}
이러한 개념은 제어의 역전(Inversion of Control, IoC)라고 불리기도 한다. 어떠한 객체를 사용할지에 대한 책임은 프레임워크에게 넘어갔고, 자신은 수동적으로 주입받는 객체를 사용하기 때문이다.
한 객체가 어떤 객체(구현 클래스)에 의존 할 것인지는 별도의 관심사이다.
Spring은 의존성 주입을 도와주는 DI 컨테이너로써, 강하게 결합 된 클래스를 분리하고, 애플리케이션 실행 시점에 객체 간의 관계를 설정해줌으로써 결합도를 낮추고 유연성을 확보해준다.
단, 한 객체가 의존 객체를 주입받을려면 반드시 DI 컨테이너에 의해 관리되고 있어야 한다.
하지만 의존 관계를 주입할 객체를 계속 생성하고 소멸한다면, 아무리 GC 성능이 좋아졌어도 부담이 된다.
그래서 Spring에서는 Bean들을 기본적으로 Singleton으로 관리한다.