Dagger 공식문서 정리하기 #1 - Dagger 소개

Dagger

Overview

  • XXXFactory, XXXLoader와 같은 클래스들이 코드에 넘쳐나게 되면 보기싫고 더럽히게 만든다. (코드의 관심사분리가 안됨)
  • 이러한 Factory클래스들을 Dependency Injection이라는 패턴으로 대체하는 역할을 한다.
  • 관심사 분리
  • jaax.inject라는 표준 어노테이션을 사용한다.
  • DI는 테스팅 뿐아니라 재사용, 범용적 모듈을 쉽게 만들기에도 좋은 패턴이다.

Dagger2와의 차이점

  • implement the full stack with generated code.

Dagger사용법

의존성 선언

의존성을 걸려면 @inject라는 어노테이션을 사용해서 의존성을 추가할 수 있다.

생성자(Constructor)에 추가하기

이 어노테이션이 걸린 파라미터 혹은 변수는 생성자에서 새로운 인스턴스 생성이 될때 필요할때에 해당값을 Dagger를 통해서 주입받게 된다.

class Thermosiphon implements Pump {
private final Heater heater;

@Inject
Thermosiphon(Heater heater) {
    this.heater = heater;
}

...
}

필드에 선언하기

필드에 직접 @Inject어노테이션을 선언하여 값을 주입받을 수도 있다. 필드에 해당하는 인스턴스는 Dagger를 통해서 주입받는다.

class CoffeeMaker {
    @Inject Heater heater;
    @Inject Pump pump;

    ...
}
  • 생성자가 아닌 필드선언을 통해서 중비을 받게 되면 요청이 왔을때 필드에 값들을 주입받는다. 하지만 새로운 인스턴스를 만들지 않는다. 파라미터가 없는 기본생성자를 추가해둔다면 Dagger는 새로운 인스턴스를 만든다.

  • 메소드 파라미터로 주입하는 방식도 지원하지만, 생성자나 필드선언이 주로 많이 선호된다.

의존성 만족하기

  • 기본적으로 Dagger는 파라미터 type을 보고 주입을한다. @Inject가 걸린 필드에서 새로운 인스턴스를 주입할때는 New CoffeeMaker(); 를 생성자로 새로운 인스턴스를 생성하여 주입한다.

  • @Inject에도 제약사항이 있다.
    • Interface는 생성되지 않는다.
    • 3rd-party 클래스는 사용해선 안된다.
    • Configuratble 객체는 먼저 configured되어야 한다.
    • 이러한 경우에는 직접 해당 타입의 인스턴스를 제공해주어야만 한다.
  • @Provides어노테이션을 사용한 메소드를 사용해서 이런 의존성에 맞는 인스턴스를 제공할 수 있다.
@Provides static Heater provideHeater() {
return new ElectricHeater();
}
  • @Binds는 타입을 변환해주는 alias처럼 쓰일수 있다.

단 이경우에는 abtract키워드를 사용한다.

@Provides static Heater provideHeater() {
return new ElectricHeater();
}

//above @Provides method is same
@Binds abstract Heater bindHeater(ElectricHeater impl);
  • 모든 @Provides, @Binds 메소드는 반드시 Module을 안에 선언되어야 한다.
@Module
interface HeaterModule {
    @Binds abstract Heater bindHeater(ElectricHeater impl);
}

Building the Graph

@Inject, @Provides를 통해서 객체주입의 의존성을 정의했었다면 이런 의존 그래프에 접근할수 있는 잘 정의된 무언가 가 필요하다. 이 무언가를 컴포넌트라고 하는데.. Dagger2에서는 interface를 통해서 정의한다. 이 인터페이스는 파라미터는 없고, 특정 타입을 리턴하는 형태로 정의된다. @Component 를 통해서 이런 셋을 정의할수 있다. modules 파라미터에 어떤 모듈을 사용할지 정해주면 Dagger2 의존성 그래프에 맞게 객체를 공급하는 코드를 자동생성한다.

//XXX.class
@Component 
interface XXX{ }


//When Use XXX
XXX =  new DaggerXXX.builder()....build();

// DaggerFoo_Bar_BazComponent
class Foo {
   static class Bar { 
   	 @Component
     interface BazComponent();
   }
}
  • Those declared by @Provides methods within a @Module referenced directly by @Component.modules or transitively via @Module.includes
  • Any type with an @Inject constructor that is unscoped or has a @Scope annotation that matches one of the component’s scopes
  • The component provision methods of the component dependencies
  • The component itself
  • Unqualified builders for any included subcomponent
  • Provider or Lazy wrappers for any of the above bindings
  • A Provider of a Lazy of any of the above bindings (e.g., Provider<Lazy<CoffeeMaker>>)
  • A MembersInjector for any type

Singleton 과 Scope

Dagger2를 통해 주입되는 인스턴스의 Scope를 다음 어노테이션들을 통해서 정할 수 있다.

Singleton

@Singleton 을 통해서 쓰레드간에 공유되는 유일한 인스턴스를 제공해줄수도 있다. @Provides 메소드, @Component 인터페이스 모두 사용이 가능하다.

Resuable Scope

재사용이 가능한 스코프를 제공해줄수도 있다. @Reusable 어노테이션을 사용하면 다른 스코프와는 다르게 그 바인딩은 하나의 컴포넌트에 종속되지 않는다.대신에 각가의 컴포넌트는 해당 객체의 인스턴스를 캐시해둔다.

안드로이드와 같이 객체생성 비용이 많이 드는 환경에서는 이와 같은 재사용은 매우 효율적이다.

하지만 이런 바인딩이 딱 한번 불린다는 보장은 할수 없으므로, @Reusable 바인딩이 mutable 하거나 동일한 인스턴스를 참조하는 객체를 제공하는것은 위험하다. (이런 경우에는 명확히 스코프를 지정해주도록하자) 재사용은 immutable객체를 리턴하는 경우에 사용하도록 하고 되도록 남용하지 말도록하자.

Lazy Injection

Lazy<T> 를 사용하면 Lazy Inejction을 사용할수 있다. 이는 해당 값이 실제로 사용되기 전에는 인스턴스 생성이 되지 않고, 실제로 사용될때 인스턴스가 생성되게 한다.

Provider Injections

이따금씩 코드내에서 하나의 인스턴스만 사용하지 않고 인스턴스를 새로 생성하여 사용해야하는 경우가 있다. 이런 경우에는 Factory, Builder와 같은 패턴을 사용할수도 있지만.. Provider<T> 를 주입해주면서도 가능하다.

Privder의 잦은사용은 코드에서 혼란을 초래할 수도 있다.

Optional bindings

@BindOptionalOf abstract CoffeCozy optionalCozy();

//when use it
@Inject
public Optional<CoffeCozy> coffeCozy;

Qualifiers

@Qualifiers, @Named 여러개의 변수에 동일한 타입을 넘겨줄때 구분해서 주입을 해야할때 사용한다.