[Angular] DI를 도와주는 인젝터(Injector), 인젝터 트리(Injector Tree)

2022. 2. 9. 15:16Angular/Information

의존성 주입 요청에 의해 주입되어야 할 의존성 인스턴스가 있다면 Angular는 이 인스턴스의 주입을 인젝터(Injector)에 요청하게 된다.

이때 인젝터에 대해 알아볼 것이며, 인젝터 트리(Injector Tree)에 대해서도 알아볼 것이다.

인젝터(Injector), 무슨 역할을 하며 어떻게 작동할까?

인젝터는 컴포넌트와 모듈 레벨로 존재한다.

의존성 주입 요청에 의해 프로바이더를 검색하며 인스턴스를 생성하여 의존성 인스턴스를 주입한다.

 

의존성 주입 요청이 있을 때 마다 매번 의존성 인스턴스를 생성하는 것은 아니다.

인젝터는 인스턴스 풀(Instance pool)인 컨테이너를 관리하고 있다.

인젝터는 의존성 주입 요청을 받으면 프로바이더를 참조하여 요청된 인스턴스가 컨테이너에 존재하고 있는지 검색하는 것이다.

이 때 사용하는 것이 프로바이더의 provide 프로퍼티 값인 토큰이다.

기존에 생성된 인스턴스는 프로바이더의 토큰을 키로 하여 컨테이너에 저장을 한다.

인스턴스를 생성할 때에는 컨테이너에 프로바이더의 토큰을 검색하는 것이다.

providers: [{
  // 의존성 인스턴스의 타입(토큰, Token)
  provide: FooService,
  // 의존성 인스턴스를 생성할 클래스
  useClass: FooService
}]

주입 요청을 받은 의존성 인스턴스가 컨테이너 내에 존재한다면 새롭게 생성하는 것이 아닌 컨테이너에 존재하는 인스턴스를 주입해주는 것이고, 그렇지 않다면 프로바이더의 프로퍼티인 useClass를 참조하여 새롭게 생성하고, provide를 참조하여 토큰을 생성해 컨테이너에 저장을 한다.

프로바이더의 프로퍼티인 useClass는 클래스 프로바이더(Class Provider) 방식이다.
이와 다른 방법으로는 값 프로바이터(Value Provider), 팩토리 프로바이더(Factory Provider) 방식이 있다.
프로바이더에 대해 상세한 내용은 후에 서술하며, 서술이 완료되면 이 부분에 링크를 남겨놓도록 한다.

인젝터(Injector)

위 작성한 내용중 인젝터는 컴포넌트와 모듈의 레벨별로 존재한다고 작성했다.

컴포넌트는 트리 구조로 구성되며, 모든 컴포넌트는 각각 하나의 인젝터를 갖고 있기 때문에 컴포넌트 트리 구조와 동일한 인젝터 트리(Injector Tree)가 만들어질 것이다.

인젝터 트리(Injector Tree), 알아보자

위 작성한 것과 같이 구성요소에서 의존성 주입 요청이 있을 때 Angular는 요청한 구성요소의 인젝터에게 의존성 주입을 요청하게 된다.

 

만약 요청한 구성요소의 인젝터에서 의존성 인스턴스를 검색하였으나 존재하지 않는다면 상위 구성요소의 인젝터에게 의존성 주입 요청을 전달하고, 상위 구성요소의 인젝터는 이를 또 검색하게 된다.

만약 상위 인젝터에게도 없다면, 또 상위로 계속해서 올라가게 된다.

결국 루트 인젝터까지 올라가게 되며 만약 루트 인젝터에게도 존재하지 않는다면 어플리케이션에서 오류가 발생하게 된다.

 

예를 들어 아래와 같은 어플리케이션 구조가 있다고 해보자.

아래 이미지는 컴포넌트 구조와 인젝터 트리 구조를 동시에 설명한 것이다.

컴포넌트 트리(Component Tree), 인젝터 트리(Injector Tree)

ChildComponent에서 의존성 주입(Dependency Injection) 요청이 있었고, 주입 대상의 프로바이더는 루트 모듈의 인젝터에게 등록되어 있다.

ChildComponent에서 요청한 의존성 인스턴스를 주입하기 까지의 과정을 살펴보자.

  1. ChildComponent의 인젝터는 주입 요청한 의존성 인스턴스를 컨테이너에서 검색하게 된다.
    하지만, ChildComponent에는 프로바이더가 등록되어 있지 않기 때문에 검색에 실패해 ParentComponent의 인젝터에게 요청한다.
  2. ParentComponent의 인젝터는 ChildComponent 인젝터에게 요청 받은 의존성 인스턴스를 컨테이너에서 검색하게 된다.
    하지만, ParentComponent에는 프로바이더가 등록되어 있지 않기 때문에 검색에 실패해 AppComponent의 인젝터에게 요청한다.
  3. AppComponent의 인젝터는 ParentComponent 인젝터에게 요청 받은 의존성 인스턴스를 컨테이너에서 검색하게 된다.
    하지만, AppComponent에는 프로바이더가 등록되어 있지 않기 때문에 검색에 실패해 루트 모듈인 AppModule의 인젝터에게 요청한다.
  4. AppModule의 인젝터는 AppComponent 인젝터에게 요청 받은 의존성 인스턴스를 컨테이너에 검색하게 된다.
    이 때 AppModule(루트 모듈)에는 주입 대상의 프로바이더가 등록 되어 있기 때문에 이 프로바이더를 사용하여 AppModule의 인젝터는 자신이 관리하고 있는 컨테이너에서 의존성 인스턴스를 ChildComponent에 전달하게 된다.

이와 같이 의존성 주입 요청을 받게되면 Angular는 위와 같은 과정을 통해 인젝터 트리를 탐색하게 되며, 의존성 인스턴스를 주입해줄수 있는 인젝터를 찾게 되는 것이다.

이 때 의존성 인스턴스를 주입할 인젝터를 특정하는 기준이 되는 것은 프로바이더의 등록 위치이다.

위 이미지에서는 프로바이더의 등록 위치가 AppModule(루트 모듈)이기 때문에 최상위 인젝터까지 검색을 하게 된 것이다.

이는 상위 인젝터에서 하위 인젝터로 의존성 인스턴스를 제공할 수 있다는 것을 의미한다.

하지만, 하위 인젝터가 상위 인젝터로 의존성 인스턴스를 제공할 수 없다.

하위 인젝터가 상위 인젝터에게 제공하는 것은 SOLID 원칙중 의존성 역전 원칙(Dependency Inversion Principle)에 위배된다.

 

이를 통해 알 수 있는 내용은 아래 두가지다.

  • 컴포넌트의 프로바이더에 등록되어 있는 서비스는 자신과 하위 컴포넌트에 주입할 수 있는 로컬 서비스이다.
  • 루트 모듈의 프로바이더에 등록되어 있는 서비스는 어플리케이션 전역에 주입할 수 있는 전역 서비스이다.
    루트 인젝터는 단일 인스턴스를 생성하고, 이 인스턴스를 요청하는 모든 구성요소에게 싱글턴 인스턴스를 주입한다.