Dagger 官方介紹翻譯

官方介紹翻譯,原文:dagger

使用 Dagger

用一個例子來說明 Dagger 的依賴關系注入方法。完整的代碼可以編譯執(zhí)行,見coffee example.

聲明依賴

Dagger可以構造應用需要的實例和它們的依賴關系。使用 javax.inject.Inject 注解來標注哪個構造器和字段是它感興趣的部分。

Dagger應該在創(chuàng)建一個類實例時使用@Inject方法注解構造器。當一個新的實例被請求構建時,Dagger 將獲取請求的參數(shù)并調用這個構造器。

class Thermosiphon implements Pump {
  private final Heater heater;
 
  @Inject
  Thermosiphon(Heater heater) {
    this.heater = heater;
  }
 
  ...
}

Dagger 可以直接注入字段。下面這個例子中它直接獲取了Heater的實例heater和Pump的實例pump。

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

如果你的類使用了@Inject注解字段,但沒有對應的@Inject注解的構造函數(shù),那么 Dagger 會使用默認的無參構造函數(shù)(如果存在)。缺少@Inject注解的類不能被 Dagger 構造。

Dagger 不支持方法的注入。

滿足依賴

默認情況下,Dagger 通過給的參數(shù)構造實例來滿足每個依賴,如上所述。當你請求一個 CoffeeMaker, 它會獲取一個實例通過調用 new CoffeeMaker() 并設置它的可注入字段。

但是 @Inject 并不是萬能的:

*接口不能被構造
*第三方的類不能被注解
*配置類需要配置

在這些情況下 @Inject 就顯得力不從心。使用 @Provides 注解方法來滿足這些依賴關系。方法的返回類型定義了它所滿足的依賴關系。

例如,provideHeater() 會在獲取 Heater 時調用:

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

@Provides 注解的方法也可以和它們自身有依賴關系。這個例子會在 Pump 被請求時返回一個 Thermosiphon:

@Provides Pump providePump(Thermosiphon pump) {
  return pump;
}

所有的 @Provides 方法都必須屬于一個模塊。模塊只是被 @Module 注解的類。

@Module
class DripCoffeeModule {
  @Provides Heater provideHeater() {
    return new ElectricHeater();
  }
 
  @Provides Pump providePump(Thermosiphon pump) {
    return pump;
  }
}

為了方便統(tǒng)一,@Provides 注解的方法加 provide 前綴,模塊類會加 Module 后綴。

構建圖表

@Inject 和 @Provides 注解的類共同構成了整個項目圖表,聯(lián)系是它們之間的依賴關系。調用 ObjectGraph.create() 來獲取這個圖表,這個圖表接受一個或多個模塊:

1 ObjectGraph objectGraph = ObjectGraph.create(new DripCoffeeModule());

為了讓圖表投入使用我們需要引導注入(Bootstrap injection)。通常在入口類中添加一行調用注入的命令,或者是 Android 的 activity 類里。在這個 coffee 的例子里,CoffeeApp 類是用來初始化注入依賴的。向圖表請求一個有注入的實例:

class CoffeeApp implements Runnable {
  @Inject CoffeeMaker coffeeMaker;
 
  @Override public void run() {
    coffeeMaker.brew();
  }
 
  public static void main(String[] args) {
    ObjectGraph objectGraph = ObjectGraph.create(new DripCoffeeModule());
    CoffeeApp coffeeApp = objectGraph.get(CoffeeApp.class);
    ...
  }
}

現(xiàn)在還剩的一件事就是 CoffeeApp 還不為圖表所知。我們需要顯式地在注冊它,作為一個 @Module 注解的注入類型中:

@Module(
    injects = CoffeeApp.class
)
class DripCoffeeModule {
  ...
}

injects 選項允許在編譯時完成圖表。這樣使得問題的檢測更早,開發(fā)更快,減少了使用反射的風險。
現(xiàn)在,圖表已經(jīng)構造完成,根對象也注入完成,可以運行咖啡制造應用了。

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

單例

使用 @Singleton 注解一個 @Provides 方法或者可注入的類。圖表就會對其使用單例模式。

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

@Singleton 注解在一個可注入的類前也隱式地告知使用者這個類可能會在多線程中被使用。

@Singleton
class CoffeeMaker {
  ...
}

延遲注入

有時可能會需要延遲獲取一個實例。對任何綁定的 T,可以構建一個 Lazy<T> 來延遲實例化直至第一次調用 Lazy<T> 的 get() 方法。如果 T 是一個單例模式,那么 Lazy<T> 會獲取 ObjectGraph 中注入的相同實例。否則的話,每個注入都會獲取單獨的 Lazy<T> 實例。不管怎樣,之后的調用都會獲取相同的實例。

class GridingCoffeeMaker {
  @Inject Lazy<Grinder> lazyGrinder;
 
  public void brew() {
    while (needsGrinding()) {
      // Grinder created once on first call to .get() and cached.
      lazyGrinder.get().grind();
    }
  }
}

provider 注入

有些時候需要獲取多個實例而不是單一的。此時有一些選擇(工廠模式,構造模式等等),其一是注入 Provider<T> 而非 T。Provider<T> 在每次 .get() 方法調用時都會生成新的實例。

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();
      ...
    }
  }
}

Note:Provider<T> 有可能會創(chuàng)建混亂的代碼,也有可能令圖表審查錯誤或構造錯誤。許多時候你將使用 Factory<T> 或者 Lazy<T> 來重新構建代碼的架構和生命周期,作為使用 Provider<T> 的保障。常用的情況是當你需要使用和整個對象原始的生命周期無關的結構時使用這個注入。

限定注入

有時單類型不足以定義依賴關系。例如,復雜的咖啡制造機應用也許希望分開加熱水和盤子。

這個情況下,增加一個限定注解(qualifier annotation)。 這里是 @Named 的定義,在 javax.inject 下:

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

你可以創(chuàng)建你自己的限定注解,或只用 @Named 就夠了。通過對感興趣的字段或者參數(shù)添加注解來使用限定。類型和限定注解將一同定義依賴關系。

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

對相應的 @Provides 方法提供限定值。

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

依賴關系也許并不會有多種限定注解

靜態(tài)注入

<span style="color:red">警告:這個注入需要慎重使用,因為靜態(tài)依賴關系難以檢測和重用。</span>

Dagger 可以注入靜態(tài)字段。有使用@Inject 注解靜態(tài)字段的類,必須在模塊注解中填充 staticInjections 參數(shù)。

@Module(
    staticInjections = LegacyCoffeeUtils.class
)
class LegacyModule {
}
使用 ObjectGraph.injectStatics() 方法給這些靜態(tài)字段填充注入值:
ObjectGraph objectGraph = ObjectGraph.create(new LegacyModule());
objectGraph.injectStatics();

注意:靜態(tài)注入僅運行在即時圖表模塊。如果你在一個 plus() 方法創(chuàng)建的圖表中調用了 injectStatics() 方法,那么在擴展擴展的圖表模塊中靜態(tài)注入將不會執(zhí)行。

編譯時驗證

Dagger 包含了一個注解處理器( annotation processor)來驗證模塊和注入。這個過程很嚴格而且會拋出錯誤,當有非法綁定或綁定不成功時。下面這個例子缺少了 Executor:

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

當編譯時,javac 會拒絕綁定缺少的部分:

[ERROR] COMPILATION ERROR :
[ERROR] error: No binding for java.util.concurrent.Executor
               required by provideHeater(java.util.concurrent.Executor)

可以通過給方法 Executor 添加 @Provides注解來解決這個問題,或者標記這個模塊是不完整的。不完整的模塊允許缺少依賴關系。

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

模塊提供的方法中,若有注解中列出的注入類不需要的方法,也會報錯。

@Module(injects = Example.class)
class DripCoffeeModule {
  @Provides Heater provideHeater() {
    return new ElectricHeater();
  }
  @Provides Chiller provideChiller() {
    return new ElectricChiller();
  }
}

因為 Example 注入僅僅使用 Heater, javac 會拒絕未使用的綁定:

[ERROR] COMPILATION ERROR:
[ERROR]: Graph validation failed: You have these unused @Provider methods:
      1. coffee.DripCoffeeModule.provideChiller()
      Set library=true in your module to disable this check.

如果模塊的綁定將在列出的注入以外的地方使用,那么就標記這個模塊為 library。

@Module(
  injects = Example.class,
  library = true
)
class DripCoffeeModule {
  @Provides Heater provideHeater() {
    return new ElectricHeater();
  }
  @Provides Chiller provideChiller() {
    return new ElectricChiller();
  }
}

為了獲得最全面的編譯時驗證,可以創(chuàng)建一個包含了所有模塊的模塊。這樣注解處理器就會檢測所有的模塊的所有的問題然后給出報告。

@Module(
    includes = {
        DripCoffeeModule.class,
        ExecutorModule.class
    }
)
public class CoffeeAppModule {
}

在編譯 classpath 中包含 Dagger 的 jar 文件,注解處理器就自動可用了。

編譯時代碼生成

Dagger 的注解處理器也許會生成源文件例如 CoffeeMaker$InjectAdapter.java 或者 DripCoffeeModule$ModuleAdapter。這些文件是 Dagger 的履行細節(jié)。不要直接使用它們,盡管它們可以執(zhí)行單步調試。

模塊重寫

Dagger 會在有多個 @Provides 方法描述一個依賴關系時報錯,但總有一些時候是需要替換產(chǎn)品代碼來進行開發(fā)或測試。使用 overrides = true,就可以更換綁定。
這個單例測試重寫了 DripCoffeeModule 的 Heater ,替換為 Mockito。這個測試類獲取注入并用于測試。

public class CoffeeMakerTest {
  @Inject CoffeeMaker coffeeMaker;
  @Inject Heater heater;
 
  @Before public void setUp() {
    ObjectGraph.create(new TestModule()).inject(this);
  }
 
  @Module(
      includes = DripCoffeeModule.class,
      injects = CoffeeMakerTest.class,
      overrides = true
  )
  static class TestModule {
    @Provides @Singleton Heater provideHeater() {
      return Mockito.mock(Heater.class);
    }
  }
 
  @Test public void testHeaterIsTurnedOnAndThenOff() {
    Mockito.when(heater.isHot()).thenReturn(true);
    coffeeMaker.brew();
    Mockito.verify(heater, Mockito.times(1)).on();
    Mockito.verify(heater, Mockito.times(1)).off();
  }
}

重寫對于應用的小變動最合適不過:
替換真實的實現(xiàn)為模擬的用于單元測試
替換LDAP認證為假的認證協(xié)議,用于開發(fā)
對于更多更大量的變化,通常會選擇使用不同的模塊組合而非重寫。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容