퉁탕퉁탕 만들어보자

Dagger2 가이드 번역(2) 본문

Computer/Android

Dagger2 가이드 번역(2)

호숀티 2022. 9. 5. 09:15
반응형

Singletons 과 Scoped Bindings

@Provides 메서드나 주입 가능한 클래스에 @Singleton 으로 주석을 추가합니다.

그래프는 모든 클라이언트에 대해 값의 단일 인스턴스를 사용합니다.

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

주입 가능한 클래스의 @Singleton 주석은  documentation 으로써의 역할도 합니다.

미래의 잠재적인 유지 관리자에게 이 클래스가 여러 스레드에서 공유될 수 있음을 알려줍니다.

@Singleton
class CoffeeMaker {
  ...
}

 

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

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

컴포넌트에는 여러 Scope 주석이 적용될 수 있습니다. 컴포넌트는 선언하는 모든 범위와 범위가 지정된 바인딩을 포함할 수 있습니다.

재사용가능한 scope

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

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

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

컴포넌트가 바인딩을 한 번만 호출한다는 보장이 없으므로 변경 가능한 개체 또는 동일한 인스턴스를 참조하는 것이 중요한 개체를 반환하는 바인딩에 @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 주석을 추가합니다. 이것은 자체적으로 @Qualifier 주석이 있는 모든 주석입니다. 다음은 javax.inject에 포함된 qualifier 주석인 @Named의 선언입니다.

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

직접 qualifier 주석을 만들거나 아니면 @Named를 사용할 수 있습니다. type 이나 qualifier 주석은 모두 종속성을 식별하는 데 사용됩니다.

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

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

@BindsOptionalOf abstract CoffeeCozy optionalCozy();

이는 @Inject 생성자 및 멤버 및 @Provides 메서드가 Optional<CoffeeCozy> 개체에 종속될 수 있음을 의미합니다. 컴포넌트에 CoffeeCozy에 대한 바인딩이 있는 경우 Optional이 표시됩니다. CoffeeCozy에 대한 바인딩이 없으면 Optional이 없습니다.

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

  • Optional<CoffeeCozy> (unless there is a @Nullable binding for CoffeeCozy; see below)
  • Optional<Provider<CoffeeCozy>>
  • Optional<Lazy<CoffeeCozy>>
  • Optional<Provider<Lazy<CoffeeCozy>>>

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

하위 컴포넌트에 기본 유형에 대한 바인딩이 포함된 경우, 한 컴포넌트에 없는 옵셔널 바인딩이 하위 컴포넌트에 존재할 수 있습니다.

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

Instance 바인딩 하기

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

만약 앱이 @UserName 문자열로 삽입하려는 사용자 이름을 나타내는 단일 인수를 취하는 경우, @BindsInstance 주석이 달린 메서드를 컴포넌트 빌더에 추가하여 해당 인스턴스가 컴포넌트에 주입되도록 할 수 있습니다.

@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의 annotation processor는 strict해서, 바인딩이 유효하지 않거나 불완전하면 컴파일러 오류가 발생합니다. 예를 들어, 이 모듈은 Executor에 대한 바인딩이 없는 컴포넌트에 설치되었습니다.

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

컴파일타임에 javac는 누락된 바인딩을 거부합니다.

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

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 구현 세부 정보입니다. 직접 사용할 필요는 없지만 주입을 통해 단계적으로 디버깅할 때 편리할 수 있습니다. 코드에서 참조해야 하는 생성된 유형은 컴포넌트에 대해 Dagger 접두사가 붙은 유형뿐입니다.

Using Dagger In Your Build

애플리케이션 런타임에 dagger-2.X.jar를 포함해야 합니다. 코드 생성을 활성화하려면 컴파일 시 빌드에 dagger-compiler-2.X.jar를 포함해야 합니다. 더 많은 정보를 위해서 다음 파일을 참고하세요 -> README 

 

GitHub - google/dagger: A fast dependency injector for Android and Java.

A fast dependency injector for Android and Java. Contribute to google/dagger development by creating an account on GitHub.

github.com

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.
728x90
반응형