티스토리 뷰

Dagger

모든 애플리케이션에서 최고의 클래스는 다음과 같은 작업을 수행하는 클래스입니다: BarcodeDecoder, KoopaPhysicsEngineAudioStreamer. 이러한 클래스에는 종속성이 있습니다. 아마도 BarcodeCameraFinder, DefaultPhysicsEngineHttpStreamer일 것입니다.

대조적으로, 모든 응용 프로그램에서 최악의 클래스는 많은 작업을 수행하지 않고 공간을 차지하는 클래스입니다: BarcodeDecoderFactory, CameraServiceLoaderMutableContextWrapper. 이 클래스는 흥미로운 것들을 함께 연결하는 서투른 덕트 테이프입니다.

Dagger는 보일러 플레이트를 작성하는 부담 없이 종속성 주입 디자인 패턴을 구현하는 이러한 FactoryFactory 클래스를 대체합니다. 그래서 개발자가 중요한 일에만 집중할 수 있습니다. 종속성을 선언하고 이를 충족하는 방법을 지정하고 앱을 출시하세요.

표준 javax.inject 주석(JSR 330)을 기반으로 하여 각 클래스를 쉽게 테스트할 수 있습니다. RpcCreditCardService를 FakeCreditCardService로 교체하기 위해 많은 상용구가 필요하지 않습니다.

의존성 주입은 테스트만을 위한 것이 아닙니다. 또한 재사용 가능하고 교체 가능한 모듈을 쉽게 생성할 수 있습니다. 모든 앱에서 동일한 AuthenticationModule을 공유할 수 있습니다. 또한 개발 중에는 DevLoggingModule을 실행하고 프로덕션에서는 ProdLoggingModule을 실행하여 각 상황에서 올바른 동작을 얻을 수 있습니다.

Why Dagger 2 is Different

종속성 주입 프레임워크는 구성 및 주입을 위한 다양한 API와 함께 수년 동안 존재해 왔습니다. 그렇다면 바퀴를 재발명하는 이유는 무엇입니까? Dagger 2는 생성된 코드로 전체 스택을 구현한 최초의 제품입니다. 기본 원칙은 종속성 주입이 가능한 한 간단하고 추적 가능하며 성능이 좋은지 확인하기 위해 사용자가 손으로 작성한 코드를 모방하는 코드를 생성하는 것입니다. 디자인에 대한 자세한 내용은 Gregory Kick의 이 강연(슬라이드)을 시청하십시오.

Using Dagger

우리는 커피 메이커를 구축하여 의존성 주입과 Dagger를 시연할 것입니다. 컴파일하고 실행할 수 있는 전체 샘플 코드는 Dagger의 커피 예제를 참조하세요.

Declaring Dependencies

Dagger는 애플리케이션 클래스의 인스턴스를 구성하고 해당 종속성을 충족합니다. javax.inject.Inject 주석을 사용하여 관심 있는 생성자와 필드를 식별합니다.

@Inject를 사용하여 Dagger가 클래스의 인스턴스를 생성하는 데 사용해야 하는 생성자에 주석을 달 수 있습니다. 새 인스턴스가 요청되면 Dagger는 필수 매개변수 값을 얻고 이 생성자를 호출합니다.

class Thermosiphon implements Pump {
  private final Heater heater;

  @Inject // 생성자애 inject 주석
  Thermosiphon(Heater heater) {
    this.heater = heater;
  }

  ...
}

Dagger는 필드를 직접 주입할 수 있습니다. 이 예에서는 히터 필드에 대한 Heater 인스턴스와 펌프 필드에 대한 Pump 인스턴스를 얻습니다.

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

  ...
}

클래스에 @Inject 주석 필드가 있지만 @Inject 주석 생성자가 없는 경우 Dagger는 요청 시 해당 필드를 삽입하지만 새 인스턴스를 생성하지는 않습니다. Dagger가 인스턴스도 생성할 수 있음을 나타내기 위해 @Inject 주석과 함께 인수가 없는 생성자를 추가하십시오.

Dagger는 메서드 주입도 지원하지만 일반적으로 생성자 또는 필드 주입이 선호됩니다.

@Inject 주석이 없는 클래스는 Dagger로 생성할 수 없습니다.

의존성 충족

기본적으로 Dagger는 위에서 설명한 대로 요청된 유형의 인스턴스를 구성하여 각 종속성을 충족합니다. CoffeeMaker를 요청하면 new CoffeeMaker()를 호출하고 주입 가능한 필드를 설정하여 얻을 수 있습니다.

그러나 @Inject는 모든 곳에서 작동하지 않습니다.

  • 인터페이스를 구성할 수 없습니다.
  • 제3자(third party) 클래스는 주석을 달 수 없습니다.
  • 구성 가능한 개체를 구성해야 합니다!

@Inject가 충분하지 않거나 어색한 경우 @Provides-annotated 메서드를 사용하여 종속성을 충족합니다. 메서드의 반환 유형은 충족하는 종속성을 정의합니다.

예를 들어, 히터가 필요할 때마다 providerHeater()가 호출됩니다.

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

@Provides 메소드가 자체적으로 종속성을 가질 수도 있습니다. 예를 들어 ElectricHeater에는 @Inject 생성자가 있으므로 위의 메서드를 다음과 같이 대신 작성할 수 있습니다.

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

이런 식으로 Dagger는 ElectricHeater 인스턴스화를 처리하고 @Provides 메서드는 Heater 유형에 별칭을 지정하는 데만 사용됩니다.

이 특별한 경우에는 @Binds 메서드를 사용하여 별칭을 정의하여 더 단순화할 수 있습니다. @Provides와 달리 @Binds 메서드는 추상적이고 구현이 없습니다.

(ElectricHeater 에는 @Inject 생성자가 있으므로 bind를 사용하여 아래처럼 표현이 가능함.)

@Binds Heater bindHeater(ElectricHeater impl);

참고: @Binds를 사용하는 것은 별칭(alias)을 정의하는 데 선호되는 방법입니다. Dagger는 컴파일 시간에만 모듈이 필요하고 런타임에 모듈을 로드하는 클래스를 피할 수 있기 때문입니다.

(울 회사에서 쓰는 DI 중에 viewModel 을 DI 하는 경우 직접 뷰모델 팩토리를 주입받고,
거기에 더해 Provides 에서 각 (연결된)액티비티와, 뷰모델 팩토리를 파라미터로 갖는 Provider로 ViewModelProvider(activity, viewModelFactory).get() 으로 뷰모델을 주입하여 viewModel 필드에 바로 Injection 이 가능하다.)

dagger2 android viewmodel 과 사용하기
[https://www.charlezz.com/?p=1315](Dagger2를 알아보자 – Multibinding)
DAGGER2 @Binds

@Module
interface HeaterModule {
  @Binds Heater bindHeater(ElectricHeater impl);
}

관례에 따라 @Provides 메서드는 제공 접두사로 이름이 지정되고 @Binds 메서드는 bind 접두사로 이름이 지정되며 모듈 클래스는 Module 접미사로 이름이 지정됩니다.

Building the Graph

@Inject 및 @Provides-annotated 클래스는 종속성으로 연결된 개체의 그래프를 형성합니다. 애플리케이션의 기본 메서드나 Android 애플리케이션과 같은 코드를 호출하면 잘 정의된 루트 집합을 통해 해당 그래프에 액세스합니다. Dagger 2에서 해당 집합은 인수가 없고 원하는 유형을 반환하는 메서드가 있는 인터페이스로 정의됩니다. 이러한 인터페이스에 @Component 주석을 적용하고 모듈 유형을 모듈 매개변수에 전달함으로써 Dagger 2는 해당 계약의 구현을 완전히 생성합니다.

@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
  CoffeeMaker maker();
}

구현은 Dagger 접두사가 붙은 인터페이스와 동일한 이름을 갖습니다. 해당 구현에서 builder() 메서드를 호출하여 인스턴스를 얻고 반환된 빌더를 사용하여 종속성을 설정하고 새 인스턴스를 build()합니다.

CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
    .dripCoffeeModule(new DripCoffeeModule())
    .build();

참고: @Component가 최상위 유형이 아닌 경우 생성된 구성 요소의 이름에는 밑줄로 결합된 포함하는 유형의 이름이 포함됩니다. 예를 들어 이 코드는 다음과 같습니다.

class Foo {
  static class Bar {
    @Component
    interface BazComponent {}
  }
}

위 코드는 DaggerFoo_Bar_BazComponent라는 컴포넌트를 생성합니다.

액세스 가능한 기본 생성자가 있는 모든 모듈은 설정되지 않은 경우 빌더가 인스턴스를 자동으로 생성하므로 생략할 수 있습니다. 그리고 @Provides 메소드가 모두 정적인 모듈의 경우 구현에 인스턴스가 전혀 필요하지 않습니다. 사용자가 종속성 인스턴스를 생성하지 않고 모든 종속성을 구성할 수 있는 경우 생성된 구현에는 빌더를 처리할 필요 없이 새 인스턴스를 가져오는 데 사용할 수 있는 create() 메서드도 있습니다.

CoffeeShop coffeeShop = DaggerCoffeeShop.create();

이제 CoffeeApp은 Dagger가 생성한 CoffeeShop 구현을 사용하여 완전히 주입된 CoffeeMaker를 얻을 수 있습니다.

public class CoffeeApp {
  public static void main(String[] args) {
    CoffeeShop coffeeShop = DaggerCoffeeShop.create();
    coffeeShop.maker().brew();
  }
}

이제 그래프가 구성되고 진입점이 주입되었으므로 커피 메이커 앱을 실행합니다. 재미있군요.

$ java -cp ... coffee.CoffeeApp
~ ~ ~ heating ~ ~ ~
=> => pumping => =>
 [_]P coffee! [_]P

Bindings in the graph

위의 예는 좀 더 일반적인 바인딩으로 구성 요소를 구성하는 방법을 보여주지만 그래프에 바인딩을 제공하는 다양한 메커니즘이 있습니다. 다음 목록을 종속성으로 사용할 수 있으며 잘 구성된 구성 요소를 생성하는 데 사용할 수 있습니다:

  • @Component.modules에 의해 직접 참조되거나 @Module.includes를 통해 전이적으로 참조되는 @Module 내 @Provides 메소드에 의해 선언된 것들
  • 범위가 지정되지 않았거나 구성 요소의 범위 중 하나와 일치하는 @Scope 주석이 있는 @Inject 생성자가 있는 모든 유형
  • 구성 요소 종속성(component dependencies)의 구성 요소 제공 메서드(component provision methods)
  • 구성 요소(component) 자체
  • 포함된 하위 구성 요소에 대한 자격이 없는 빌더
  • 위의 바인딩에 대한 공급자 또는 지연 래퍼
  • 위 바인딩의 Lazy 제공자(예: Provider<Lazy>)
  • 모든 유형에 대한 MembersInjector

Singletons and Scoped Bindings

@Provides 메서드 또는 주입 가능한 클래스에 @Singleton으로 주석을 추가합니다. 그래프는 모든 클라이언트에 대해 값의 단일 인스턴스를 사용합니다.

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

주입 가능한 클래스의 @Singleton 주석도 문서 역할을 합니다. 잠재적인 유지 관리자에게 이 클래스가 여러 스레드에서 공유될 수 있음을 상기시킵니다.

@Singleton
class CoffeeMaker {
  ...
}

Dagger 2는 그래프의 범위가 지정된 인스턴스를 Component 구현의 인스턴스와 연결하므로 Component 자체에서 표현하려는 범위를 선언해야 합니다. 예를 들어, @Singleton 바인딩과 @RequestScoped 바인딩을 동일한 Component에 갖는 것은 의미가 없습니다. 이러한 범위는 수명 주기가 다르고 수명 주기가 다른 Component에 있어야 하기 때문입니다. Component가 주어진 범위와 연결되어 있음을 선언하려면 Component 인터페이스에 범위 주석을 적용하기만 하면 됩니다.

@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
  CoffeeMaker maker();
}

Component에는 여러 범위 주석이 적용될 수 있습니다. 이것은 그것들이 모두 동일한 범위에 대한 별칭임을 선언하므로 Component는 선언하는 모든 범위와 범위가 지정된 바인딩을 포함할 수 있습니다.

Reusable scope

때로는 @Inject 생성 클래스가 인스턴스화되거나 @Provides 메서드가 호출되는 횟수를 제한하고 싶지만 특정 구성 요소 또는 하위 구성 요소의 수명 동안 정확히 동일한 인스턴스가 사용된다고 보장할 필요는 없습니다. 이는 할당 비용이 많이 들 수 있는 Android와 같은 환경에서 유용할 수 있습니다.

이러한 바인딩의 경우 @Reusable 범위를 적용할 수 있습니다. @Reusable 범위 바인딩은 다른 범위와 달리 단일 Component와 연결되지 않습니다. 대신 실제로 바인딩을 사용하는 각 Component는 반환되거나 인스턴스화된 개체를 캐시합니다.

즉, Component에 @Reusable 바인딩이 있는 모듈을 설치했지만 하위 Component만 실제로 바인딩을 사용하는 경우 해당 하위 Component만 바인딩의 개체를 캐시합니다. 조상을 공유하지 않는 두 개의 하위 Component 각각 바인딩을 사용하는 경우 각 Component는 자체 개체를 캐시합니다. Component의 조상이 이미 객체를 캐시했다면 하위 Component는 이를 재사용합니다.

Component가 바인딩을 한 번만 호출한다는 보장이 없으므로 변경 가능한 개체 또는 동일한 인스턴스를 참조하는 것이 중요한 개체를 반환하는 바인딩에 @Reusable을 적용하는 것은 위험합니다. 할당된 횟수에 신경 쓰지 않는다면 범위를 지정하지 않은 불변 객체에 @Reusable을 사용하는 것이 안전합니다.

@Reusable // It doesn't matter how many scoopers we use, but don't waste them.
class CoffeeScooper {
  @Inject CoffeeScooper() {}
}

@Module
class CashRegisterModule {
  @Provides
  @Reusable // DON'T DO THIS! You do care which register you put your cash in.
            // Use a specific scope instead.
  static CashRegister badIdeaCashRegister() {
    return new CashRegister();
  }
}

@Reusable // DON'T DO THIS! You really do want a new filter each time, so this
          // should be unscoped.
class CoffeeFilter {
  @Inject CoffeeFilter() {}
}

Lazy injections

때로는 느리게 인스턴스화할 개체가 필요합니다. 모든 바인딩 T에 대해 Lazy<T>의 get() 메서드에 대한 첫 번째 호출까지 인스턴스화를 연기하는 Lazy<T>를 만들 수 있습니다. T가 싱글톤이면 Lazy<T>는 ObjectGraph 내의 모든 주입에 대해 동일한 인스턴스가 됩니다. 그렇지 않으면 각 주입 사이트에 자체 Lazy<T> 인스턴스가 생깁니다. 그럼에도 불구하고 Lazy<T>의 지정된 인스턴스에 대한 후속 호출은 T의 동일한 기본 인스턴스를 반환합니다.

class GrindingCoffeeMaker {
  @Inject Lazy<Grinder> lazyGrinder;

  public void brew() {
    while (needsGrinding()) {
      // Grinder created once on first call to .get() and cached.
      lazyGrinder.get().grind();
    }
  }
}

Provider injections

때로는 단일 값을 주입하는 대신 여러 인스턴스를 반환해야 합니다. 여러 옵션(Factories, Builders 등)이 있지만 한 가지 옵션은 T 대신 Provider<T>를 삽입하는 것입니다. Provider<T>는 .get()이 호출될 때마다 T에 대한 바인딩 논리를 호출합니다. 해당 바인딩 논리가 @Inject 생성자이면 새 인스턴스가 생성되지만 @Provides 메서드에는 그러한 보장이 없습니다.

class BigCoffeeMaker {
  @Inject Provider<Filter> filterProvider;

  public void brew(int numberOfPots) {
  ...
    for (int p = 0; p < numberOfPots; p++) {
      maker.addFilter(filterProvider.get()); //new filter every time.
      maker.addCoffee(...);
      maker.percolate();
      ...
    }
  }
}

Qualifiers

때때로 type만으로는 종속성을 식별하는 데 충분하지 않습니다. 예를 들어, 정교한 커피 메이커 앱은 물과 핫 플레이트를 위한 별도의 히터를 원할 수 있습니다.

이 경우 한정자 주석을 추가합니다. 이것은 자체적으로 @Qualifier 주석이 있는 모든 주석입니다. 다음은 javax.inject에 포함된 한정자 주석인 @Named의 선언입니다.

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
  String value() default "";
}

고유한 한정자 주석을 만들거나 @Named를 사용할 수 있습니다. 관심 있는 필드 또는 매개변수에 주석을 달아 한정자를 적용합니다. 유형 및 한정자 주석은 모두 종속성을 식별하는 데 사용됩니다.

class ExpensiveCoffeeMaker {
  @Inject @Named("water") Heater waterHeater;
  @Inject @Named("hot plate") Heater hotPlateHeater;
  ...
}

해당 @Provides 메서드에 주석을 달아 정규화된 값을 제공합니다.

@Provides @Named("hot plate") static Heater provideHotPlateHeater() {
  return new ElectricHeater(70);
}

@Provides @Named("water") static Heater provideWaterHeater() {
  return new ElectricHeater(93);
}

종속성에 여러 한정자 주석이 없을 수 있습니다.

Optional bindings

component에 일부 종속성이 바인딩되지 않은 경우에도 바인딩이 작동하도록 하려면 @BindsOptionalOf 메서드를 모듈에 추가할 수 있습니다.

@BindsOptionalOf abstract CoffeeCozy optionalCozy();

즉, @Inject 생성자 및 멤버 및 @Provides 메서드는 Optional<CoffeeCozy> 개체에 종속될 수 있습니다. component에 CoffeeCozy에 대한 바인딩이 있는 경우 Optional이 표시됩니다. CoffeeCozy에 대한 바인딩이 없으면 Optional이 없습니다.

특히 다음 중 하나를 주입할 수 있습니다.

  • Optional (CoffeeCozy에 대한 @Nullable 바인딩이 없는 한; 아래 참조)
  • Optional<Provider>
  • Optional<Lazy>
  • Optional<Provider<Lazy>>

(이 중 Provider나 Lazy 또는 Provider of Lazy를 주입할 수도 있지만 그다지 유용하지는 않습니다.)

CoffeeCozy에 대한 바인딩이 있고 해당 바인딩이 @Nullable이면 Optional에 null을 포함할 수 없기 때문에 Optional를 삽입하는 것은 컴파일 타임 오류입니다. Provider와 Lazy는 항상 get() 메서드에서 null을 반환할 수 있기 때문에 항상 다른 형식을 삽입할 수 있습니다.

component에 없는 선택적 바인딩은 subcomponent에 underlying type에 대한 바인딩이 포함되어 있는 경우 subcomponent에 존재할 수 있습니다.

Guava의 Optional 또는 Java 8의 Optional을 사용할 수 있습니다.

Binding Instances

component를 빌드할 때 사용할 수 있는 데이터가 있는 경우가 많습니다. 예를 들어 명령줄 인수를 사용하는 응용 프로그램이 있다고 가정합니다. component에서 해당 인수를 바인딩할 수 있습니다.

아마도 앱은 @UserName 문자열로 삽입하려는 사용자 이름을 나타내는 단일 인수를 취합니다. @BindsInstance 주석이 달린 메서드를 구성 요소 빌더에 추가하여 해당 인스턴스가 component에 주입되도록 할 수 있습니다.

@Component(modules = AppModule.class)
interface AppComponent {
  App app();

  @Component.Builder
  interface Builder {
    @BindsInstance Builder userName(@UserName String userName);
    AppComponent build();
  }
}

그러면 앱은 다음과 같이 보일 수 있습니다.

public static void main(String[] args) {
  if (args.length > 1) { exit(1); }
  App app = DaggerAppComponent
      .builder()
      .userName(args[0])
      .build()
      .app();
  app.run();
}

위의 예에서 구성 요소에 @UserName 문자열을 삽입하면 이 메서드를 호출할 때 빌더에 제공된 인스턴스가 사용됩니다. 구성 요소를 빌드하기 전에 모든 @BindsInstance 메서드를 호출하여 null이 아닌 값을 전달해야 합니다(아래 @Nullable 바인딩 제외).

@BindsInstance 메서드에 대한 매개변수가 @Nullable로 표시되면 @Provides 메서드가 nullable인 것과 같은 방식으로 바인딩이 "nullable"로 간주됩니다. 주입 사이트도 @Nullable로 표시해야 하며 null은 허용되는 값입니다. 바인딩. 또한 Builder 사용자는 메서드 호출을 생략할 수 있으며 구성 요소는 인스턴스를 null로 처리합니다.

@BindsInstance 메소드는 생성자 인수가 있는 @Module을 작성하고 해당 값을 즉시 제공하는 것보다 선호되어야 합니다.

Compile-time Validation

Dagger 주석 프로세서는 엄격하며 바인딩이 유효하지 않거나 불완전하면 컴파일러 오류가 발생합니다. 예를 들어, 이 모듈은 Executor에 대한 바인딩이 없는 구성 요소에 설치됩니다.

@Module
class DripCoffeeModule {
  @Provides static Heater provideHeater(Executor executor) {
    return new CpuHeater(executor);
  }
}

Dagger 주석 프로세서는 엄격하며 바인딩이 유효하지 않거나 불완전하면 컴파일러 오류가 발생합니다. 예를 들어, 이 모듈은 Executor에 대한 바인딩이 없는 component에 설치됩니다.

[ERROR] COMPILATION ERROR :
[ERROR] error: java.util.concurrent.Executor cannot be provided without an @Provides-annotated method.

component의 모듈에 Executor용 @Provides-annotated 메소드를 추가하여 문제를 수정하십시오. @Inject, @Module 및 @Provides 주석은 개별적으로 유효성이 검사되지만 바인딩 간의 관계에 대한 모든 유효성 검사는 @Component 수준에서 발생합니다. Dagger 1은 @Module 수준 유효성 검사(런타임 동작을 반영하거나 반영하지 않을 수 있음)에 엄격하게 의존했지만 Dagger 2는 전체 그래프 유효성 검사를 위해 이러한 유효성 검사(및 @Module에 대한 동반 구성 매개변수)를 생략합니다.

Compile-time Code Generation

Dagger의 주석 처리기는 CoffeeMaker_Factory.java 또는 CoffeeMaker_MembersInjector.java와 같은 이름을 가진 소스 파일을 생성할 수도 있습니다. 이 파일은 Dagger 구현 세부 정보입니다. 직접 사용할 필요는 없지만 주입을 통해 단계적으로 디버깅할 때 편리할 수 있습니다. 코드에서 참조해야 하는 생성된 유형은 component에 대해 Dagger가 접두사로 붙은 유형뿐입니다.

Using Dagger In Your Build

애플리케이션 런타임에 dagger-2.X.jar를 포함해야 합니다. 코드 생성을 활성화하려면 컴파일 시 빌드에 dagger-compiler-2.X.jar를 포함해야 합니다. 자세한 내용은 README를 참조하십시오.

License

Copyright 2012 The Dagger Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

마치며

다음 글 에서는 안드로이드 에서 Dagger2 를 사용하는 방법에 대해서 알아봅니다.

Reference

https://dagger.dev/dev-guide/

댓글
최근에 올라온 글
최근에 달린 댓글
네이버 이웃추가
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함