목차
- 제어의 역전, IoC
- 의존관계(Dependency)란?
- DI(Dependency Injection)이란?
- DL(Dependency Lookup)이란?
1. 제어의 역전, IoC
먼저, 제어의 역전(Inversion of Control)이라는 개념에 대해서 알아보겠습니다. 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라 합니다.
기존의 제어 방식은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고, 실행했습니다. 하지만, 이 방식은 계층간의 결합성이 높아서 만약 한 개의 객체를 변경하면, 연결된 다른 객체들을 수정해주어야 합니다. 이때문에 계층간의 의존관계의 결합도를 낮추고, 유지보수성을 향상시키기 위해 제어의 역전(IoC)이 등장했습니다. IoC 방식은 IoC 컨테이너(외부)에서 필요로 하는 객체를 생성하여 사용자에게 객체를 주는 방식으로 동작합니다.
스프링 프레임워크에서는 객체의 생명주기 및 타 객체와의 의존관계 설정을 객체 자신이 아닌 어플리케이션 컨텍스트(혹은 IoC 컨테이너, 빈 팩토리 등으로 표현)로 해줍니다.
IoC에는 위의 그림과 같이 크게 DL과 DI가 있습니다.
2. 의존관계(Dependency)란?
의존관계는 의존대상 B가 변하면 그것이 A에 영향을 미칠 때 A는 B와 의존관계라고 합니다.
쉽게말해 B가 변경되었을 때, A가 영향을 받는 관계를 의존관계라고 합니다. 쉬운 예시를 들어보도록 하겠습니다.
치킨 가게의 요리사는 치킨 레시피에 의존합니다. 치킨 레시피가 변경되었다면, 변경된 레시피에 따라 요리사는 치킨 만드는 방법을 수정해야 합니다. 레시피의 변화가 요리사에게 영향을 미쳤기 때문에 요리사는 레시피에 의존한다고 할 수 있습니다.
public class ChickenChef{
private ChickenRecipe chickenRecipe;
public ChickenChef() { //생성자
this.chickenRecipe = new ChickenRecipe();
}
}
위 코드를 보겠습니다. ChickenChef 객체는 ChickenRecipe 객체에 의존 관계가 있습니다. 이러한 구조는 다음과 같은 문제점을 가집니다.
- 두 클래스의 결합성이 높다
- 객체들 간의 관계가 아닌 클래스 간의 관계가 맺어진다.
3. DI(Dependency Injection)이란?
의존관계 주입(Dependency Injection)은 의존 관계를 외부에서 결정(주입)해주는 방식을 말합니다.
public class ChickenChef{
private final ChickenRecipe chickenRecipe;
//@Autowired
public ChickenChef(ChickenRecipe chickenRecipe) { //생성자
this.chickenRecipe = chickenRecipe;
}
}
위와 같이 생성자에 의존성을 포함시켜 객체를 생성하는 방법을 생성자 주입이라고 합니다. 어떤 객체를 주입할 지는 런타임 시점에 스프링 컨테이너(외부)가 결정하며 컨테이너는 객체 레퍼런스를 제공해줍니다.
4. DL(Dependency Lookup)이란?
의존관계 검색(Dependency Lookup)은 의존관계가 필요한 객체에서 직접 검색하는 방식을 말합니다. 자신이 필요로 하는 의존 오브젝트를 능동적으로 찾지만, 어떤 클래스의 오브젝트를 이용할지를 결정하지는 않습니다. (IoC 방식이기 때문)
의존관계 검색은 어떤 오브젝트를 사용할 지 결정하는 것과 오브젝트의 생성은 외부 컨테이너에게 맡기지만, 이를 가져올 때는 생성자를 통한 주입 대신 스스로 컨테이너에게 요청하는 방법을 사용합니다.
따라서 의존관계가 필요한 객체(클라이언트)는 의존하고자 하는 인터페이스 타입만 지정해서 검색할 뿐 해당 인터페이스를 구현한 구체적인 클래스 객체에 대한 결정과 해당 객체에 대한 생명 주기는 IoC 컨테이너에서 책임집니다.
getBean()
의존관계에 검색에 사용되는 대표적 메소드입니다.
public class ChickenChef{
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
private ChickenRecipe chickenRecipe;
public ChickenChef() { //생성자
this.chickenRecipe = ac.getBean(ChickenRecipe.class);
}
}
실행시켜보면 앞의 DI와 동일하게 실행되는 것을 알 수 있습니다.
그런데 의존관계 주입이 코드가 훨씬 깔끔한데 굳이 의존관계 검색을 사용하는 이유가 있을까요?
의존관계 검색 방법은 코드 안에 오브젝트 팩토리 클래스나, 스프링 API가 나타나게 됩니다. 애플리케이션 컴포넌트가 컨테이너와 같이 성격이 다른 오브젝트에 의존하게 되는 것이므로 그다지 바람직하지 않습니다. 또한, 스프링은 IoC와 DI 컨테이너를 적용했다고 하더라도 애플리케이션의 기동 시점에서 적어도 한 번은 의존관계 검색 방식을 사용해 오브젝트를 가져와야 합니다. 왜냐하면, 정적 메소드인 main() 에서는 DI를 이용해 오브젝트를 주입받을 방법이 없기 때문입니다.
의존관계 주입 vs 의존관계 검색
의존관계 주입 : 의존을 하는 오브젝트와 의존을 받는 오브젝트가 모두 스프링 빈이어야 한다.
의존관계 검색 : 검색하는 오브젝트는 스프링 빈일 필요가 없다. 단, 의존을 받는 오브젝트는 스프링의 빈이어야 한다.
3. 결론
DI는 의존성 주입을 의미합니다. 각 클래스 간의 의존성을 자신이 아닌 외부(컨테이너)에서 주입합니다.
DL은 의존성 검색을 의미합니다. 직접 필요한 의존관계를 찾습니다.
정의 | 특징 | 예시 | |
DI (Dependency Injection) | 의존성 주입 | 의존대상(사용할 객체)를 주입을 통해 받는 방식 | 생성자를 이용한 의존관계 주입 |
DL (Dependency Lookup) | 의존성 검색 | 의존대상(사용할 객체)를 검색(lookup)을 통해 반환받는 방식 | ac.getBean(appconfig.class); pro.getObject(); |
참고