Dagger 官方介绍翻译

官方介绍翻译,原文:dagger

使用 Dagger

用一个例子来说明 Dagger 的依赖关系注入方法。完整的代码可以编译执行,见coffee example.

声明依赖

Dagger可以构造应用需要的实例和它们的依赖关系。使用 javax.inject.Inject 注解来标注哪个构造器和字段是它感兴趣的部分。

Dagger应该在创建一个类实例时使用@Inject方法注解构造器。当一个新的实例被请求构建时,Dagger 将获取请求的参数并调用这个构造器。

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注解的构造函数,那么 Dagger 会使用默认的无参构造函数(如果存在)。缺少@Inject注解的类不能被 Dagger 构造。

Dagger 不支持方法的注入。

满足依赖

默认情况下,Dagger 通过给的参数构造实例来满足每个依赖,如上所述。当你请求一个 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;
  }
}

为了方便统一,@Provides 注解的方法加 provide 前缀,模块类会加 Module 后缀。

构建图表

@Inject 和 @Provides 注解的类共同构成了整个项目图表,联系是它们之间的依赖关系。调用 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);
    ...
  }
}

现在还剩的一件事就是 CoffeeApp 还不为图表所知。我们需要显式地在注册它,作为一个 @Module 注解的注入类型中:

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

injects 选项允许在编译时完成图表。这样使得问题的检测更早,开发更快,减少了使用反射的风险。
现在,图表已经构造完成,根对象也注入完成,可以运行咖啡制造应用了。

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> 有可能会创建混乱的代码,也有可能令图表审查错误或构造错误。许多时候你将使用 Factory<T> 或者 Lazy<T> 来重新构建代码的架构和生命周期,作为使用 Provider<T> 的保障。常用的情况是当你需要使用和整个对象原始的生命周期无关的结构时使用这个注入。

限定注入

有时单类型不足以定义依赖关系。例如,复杂的咖啡制造机应用也许希望分开加热水和盘子。

这个情况下,增加一个限定注解(qualifier annotation)。 这里是 @Named 的定义,在 javax.inject 下:

@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") Heater provideHotPlateHeater() {
  return new ElectricHeater(70);
}
 
@Provides @Named("water") Heater provideWaterHeater() {
  return new ElectricHeater(93);
}

依赖关系也许并不会有多种限定注解

静态注入

<span style="color:red">警告:这个注入需要慎重使用,因为静态依赖关系难以检测和重用。</span>

Dagger 可以注入静态字段。有使用@Inject 注解静态字段的类,必须在模块注解中填充 staticInjections 参数。

@Module(
    staticInjections = LegacyCoffeeUtils.class
)
class LegacyModule {
}
使用 ObjectGraph.injectStatics() 方法给这些静态字段填充注入值:
ObjectGraph objectGraph = ObjectGraph.create(new LegacyModule());
objectGraph.injectStatics();

注意:静态注入仅运行在即时图表模块。如果你在一个 plus() 方法创建的图表中调用了 injectStatics() 方法,那么在扩展扩展的图表模块中静态注入将不会执行。

编译时验证

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

为了获得最全面的编译时验证,可以创建一个包含了所有模块的模块。这样注解处理器就会检测所有的模块的所有的问题然后给出报告。

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

在编译 classpath 中包含 Dagger 的 jar 文件,注解处理器就自动可用了。

编译时代码生成

Dagger 的注解处理器也许会生成源文件例如 CoffeeMaker$InjectAdapter.java 或者 DripCoffeeModule$ModuleAdapter。这些文件是 Dagger 的履行细节。不要直接使用它们,尽管它们可以执行单步调试。

模块重写

Dagger 会在有多个 @Provides 方法描述一个依赖关系时报错,但总有一些时候是需要替换产品代码来进行开发或测试。使用 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();
  }
}

重写对于应用的小变动最合适不过:
替换真实的实现为模拟的用于单元测试
替换LDAP认证为假的认证协议,用于开发
对于更多更大量的变化,通常会选择使用不同的模块组合而非重写。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容