Dagger2 User's Guide (用户指南) 全文翻译。

Dagger2实现原理分析 Dagger是一个依赖注入框架,这个框架往往给人一种“高端,但是却很难用“的感觉。我们在使用或者阅读别人的项目时,往往会陷入dagger搭建的”迷宫里”绕不出来。导致Dagger被认为是android史上”最受冷落的优质库”。本文先通过理清依赖注入的概念,然后详细介绍Dagger一些常见的使用场景和注意事项,最后介绍一下Dagger主要功能实现的原理。帮助大家进行理解。 阅读详情

原文地址

https://google.github.io/dagger/users-guide

翻译很累,转载请注明出处。
适合使用过一段时间Dagger2的人看。
可能会用到的示例。下载
官方github上的示例链接:https://github.com/google/dagger/tree/master/examples/simple/src/main/java/coffee
有不对或者不合适的地方请指出 以免误人子弟。

Dagger2用户指南

应用中最好的类是那些真正做事情的类。比如:BarcodeDecoder (条码解码器),KoopaPhysicsEngine (koopa 物理引擎) 和 AudioStreamer (音频流)。这些类会可能会依赖于其他类。
比如:BarcodeCameraFinder (条形码相机取景器),DefaultPhysicsEngine (默认的物理引擎) 或者 HttpStreamer。

与之相对的,应用中最差的类,是那些占用空间却不做事情的。
比如:BarcodeDecoderFactory (条码解码器工厂)、CameraServiceLoader (相机服务加载器) 和 MutableContextWrapper (不定上下文包装器)。这些类并没有做一些实际的事情,只是起到将有用的类链接起来的作用。

Dagger是这些什么什么工厂类之类的替代者,Dagger实现了依赖注入的设计模式,却不用关心使用依赖注入所需要的各种模板(Dagger框架已经做好了这一步)。这样我们就可以只需要将目光聚焦在真正做事情的类上。声明这些类需要的依赖,然后为这些类提供依赖,接下来就可以运行程序了。

通过构建一个标准的Java注解,就可以很方便的测试类,不需要通过一堆样板将 RpcCreditCardService 转换成 FakeCreditCardService。

依赖注入不仅仅是为了测试,它使得创建高复用性,高可替换性的模块变得更容易。这样就可以在不同的应用之间共享同一个 AuthenticationModule 。(译者附加:只要一个类满足单一职责原则,那么这个类就可以在不同地方多次使用,如果这个类还满足接口隔离原则与依赖倒置原则,那么在使用该类的地方,就可以方便的替换成另一个类。) 这样你就可以在开发环境中使用 DevLoggingModule ,在生产环境中使用 ProdLoggingModule,根据不同的环境使用不同的类来完成正确的事情。

为什么Dagger2是与众不同的

依赖注入框架已经存在了很多年,可以给各种各样的API提供配置和注入功能。那么为什么重新发明轮子(我对画轮子的理解:对已经存在的实现某种功能的框架,根据其能完成的功能,重新创建/设计框架)?Dagger2是第一个通过生成的代码实现完整的堆栈的框架。原理是生成一些模仿用户可能会手写的代码,使得依赖注入变得简单、可追踪、高效。有关更多设计的背景,查看Gregory Kick 的演讲(幻灯片)

使用Dagger2

我们通过构建一个咖啡机来演示依赖注入与Dagger。有关可以编译与运行的完整示例代码,查看 咖啡示例,或者直接下载

声明依赖

Dagger 为我们应用程序中的类构建实例,并且满足这些实例的依赖(为这些实例提供依赖)。使用javax.inject.Inject 注解来标识它感兴趣的构造器和字段。(使用 @Inject 注解标识构造方法或成员变量)。

使用 @Inject 注解标注需要使用Dagger来创建实例的类的构造方法。当需要创建一个新的实例时,Dagger会获得需求的参数值,并且调用这个构造方法。(构造方法中的参数,Dagger获得后会自动传入)。

public class Thermosiphon implements Pump {
    private final Heater heater;

    /**
     * 使用 @Inject 注解标注了构造方法
     * @param heater 参数Dagger获得后会自动传入
     */
    @Inject
    public Thermosiphon(Heater heater) {
        this.heater = heater;
    }
}

Dagger 能够准确的注入属性。在下面的例子中,它获得一个 Heater 实例并赋值给 heater 成员变量,也会获得一个 Pump 实例赋值给 pump 成员变量。

public class CoffeeMaker {

    /**
     * 使用@Inject注解标注了属性
     */
    @Inject 
    Heater heater;

    @Inject
    Pump pump; 
}

如果你的类有 @Inject 注解属性,但是没有使用 @Inject 注解构造方法,如果有需求的话,Dagger会注入这些属性,但是不会创建新的对象(暂时没懂)。如果想让Dagger能够创建对象,可以添加一个被 @Inject 注解标注的无参数的构造方法。

Dagger 也支持方法注入,尽管构造方法注入或属性注入是典型的首选。

没有 @Inject 的类不能通过Dagger构建对象。

满足依赖(上面是声明,这里就是注入了)

默认情况下,Dagger通过构造一个如上所述的请求类型的实例来满足每个依赖关系。当你需要一个 CoffeeMaker ,Dagger 会调用 new CoffeeMaker() 并且设置(set) CoffeeMaker 的可注入字段,来获得一个实例。

但是 @Inject 不是什么地方都能用。

  • 接口不能被构造。(这个应该都ok)
  • 第三方的类不能被注解。
  • 必须配置可配置对象。(啥意思?)

对于 @Inject 不足或者笨拙的情况(上述三种示例),使用 @Provides 注解标注方法来提供依赖(满足依赖)。方法的返回值类型定义了它满足的依赖关系。(比如类A中有个属性B需要一个方法来提供依赖,这个方法的返回值就应该是B的类型)

例如, 当需要一个 Heater 对象时,那么就会调用 provideHeater()

@Module(includes = PumpModule.class)
public class DripCoffeeModule {
    @Provides static Heater provideHeater(){
        return new ElectricHeater();
    }
}

@Provides 标注的方法也可能存在自己的依赖关系,如果需要 Pump 对象,就按下面代码写。

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

所有的 @Provides 方法必须属于一个module,这个module就是一个被 @Module 注解标注的类。

/**
 * 一个普通的类,被 @Module 修饰,就变成了 Dagger框架中的 module
 */
@Module
class DripCoffeeModule {
  @Provides 
  @Singleton
  static Heater provideHeater() {
    return new ElectricHeater();
  }

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

按照惯例,@Provides 修饰的方法的方法名,以 provide 为前缀。 @Module 修饰的类的类名,以 Module 为后缀。

构建图(一直在说对象图,终于能看到到底是啥了,是数据结构中的图吗?)

@Inject 注解和 @Provides 注解标注的类会组成一个对象图,类与类之间通过依赖关系关联彼此。调用类似于(应用程序的 main 方法或Android的 Application 都是程序的入口)的代码,通过明确定义的根集合来访问该图形(对象图)(根集合是什么?)。在Dagger2中,这个集合由一个接口定义,该接口中的方法没有参数,并且返回值为我们所需的类型(也就是所需的依赖的类型)。通过将 @Component 注解标注于此类接口,并将模块类型(module的类型)传递给modules参数(是 @Component 注解的参数),Dagger2就能够完整的帮助我们构建根集合。

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

生成的实现类具有同样的格式的名字,以 Dagger 为前缀,后面拼接上该接口的接口名。通过调用生成类的 builder() 方法,可以获得一个 builder 实例,使用获得的这个 builder 实例可以设置依赖,然后调用 build() 方法创建一个新的实例(这个才是我们真正想要的依赖)。

CoffeeShop coffeeShop = 
    DaggerCoffeeShop.builder()//建造者模式?获得 builder
    .dripCoffeeModule(new DripCoffeeModule())//调用builder的方法设置依赖
    .build();//调用build方法完成构建

注意:如果你的 @Component 标注的类,不是最顶级的类(就是内部类呗),那么生成的 component 组件的名称会包含该类的外部类的名称,通过下划线拼接将它们拼接起来。示例如下:

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

会生成一个叫做 DaggerFoo_Bar_BazComponent 的 component 组件。

任何具有可访问的默认构造方法的 module 组件都可以被省略,因为如果没有设置(不将 module 对象设置到 builder 里),builder (建造器)将自动构建实例。如果 module 模块中,被 @Provides 注解标注的方法都是静态的,那么 @Component 注解标注的 component 组件(是一个接口)的实现不需要创建实例。如果所有的依赖关系,都可以在不创建依赖实例的情况下构建,那么(Dagger)生成的实现( component 接口的实现)也会有一个 create() 方法,通过该方法可以获得一个新的实例,而略过 builder (不需要使用 builder)。

//DaggerCoffeeShop就是Dagger2为我们生成的 component 组件的实现类
//正常情况下,是需要调用 builder() 然后将 module 都设置到 建造器中的(builder)
//但是上面所述情况就可以跳过 builder ,跳过设置 module 的步骤,直接使用 create() 方法即可
CoffeeShop coffeeShop = DaggerCoffeeShop.create();

CoffeeShop 是 CoffeeApp 类的一个内部接口,被 @Component 注解标注。这样,CoffeeApp类,就可以简单的使用Dagger为我们生成的 CoffeeShop 的实现类对象来获得完全注入的 CoffeeMaker 。

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

    public static void main(String[] args) {
        CoffeeShop coffeeShop = DaggerCoffeeApp_CoffeeShop.create();
        //maker()后会得到CoffeeMaker对象,然后调用CoffeeMaker对象的brew()方法
        coffeeShop.maker().brew();
    }
}

现在已经构建好了对象图,也将切入点注入(就是注入了依赖),运行程序如下所示:

关于图中的绑定

上述的例子展示了怎么使用一些更典型的绑定( binding )构建一个 component 组件,但是也有多种机制可以为对象图提供绑定功能。以下可用作依赖项,也可用于生成格式良好的组件:

  • 那些直接被 @Component 注解的 modules 属性引用的 @Module 模块中被 @Provides 标注的方法 或者 传递给 @Module 注解的 includes 属性。(这句话是真的没看懂,原文在下面)
  • Those declared by @Provides methods within a @Module referenced directly by @Component.modules or transitively via @Module.includes
  • 任何一个被 @Inject 注解标注构造方法的类型,不管该类型是否被一个 @Scope 注解标注,都会与一个 component 组件的 域(scope)匹配。
  • 联系组件依赖关系的组件提供方式
  • 组件本身
  • Unqualified builders for any included subcomponent(任何被包含的子组件不适合构建者?)
  • Provider 或者 Lazy 用于上述任何绑定的包装器
  • 上述任何绑定的 Lazy 的 Provider(例子:Provider<Lazy<CoffeeMaker>>)(????)
  • 一个可以用于任意类型的 MembersInjector

Singletons and Scoped Bindings (单例与域的绑定)

使用 @Singleton 注解标注一个 @Provides 的方法或者可注入的类。该对象图将会为它所有的客户端提供一个该类型的单例实例。(那么这个图的范围是什么?是 component 组件吗?)

/**
 * 如果下载了上述的demo项目,可以尝试把 @Singleton去掉后运行程序
 * 会发现输出日志中会少一行:=> => pumping => =>
 * 是因为在输出的时候做了一个判断,判断 heater.isHot() 是否为true
 * 没输出说明不是true
 * 但是会发现输出了:~ ~ ~ heating ~ ~ ~,会发现存在:this.heating = true
 * 明明设置为true了,判断的时候为什么不通过?
 * 可以尝试在 `Thermosiphon.pump()` 中输出 heater
 * 在 `ElectricHeater.on()` 中输出 this
 * 会发现是两个对象
 * 在使用Dagger2时要尤其注意Scope
 */
@Provides 
@Singleton 
static Heater provideHeater() {
  return new ElectricHeater();
}

标注在可注入类上的注解 @Singleton 也可标注在 文档(documentation)上。它提醒潜在的维护者这个类可能被多个线程共享。(就是表示这个类会被多个地方使用,希望这些地方使用这个类的时候,用的都是同一个对象。把 “这些地方” 就可以理解成一个域,表示一个范围,也就是在这个范围中,这个类只有一个对象,也就是单例。一定要注意范围。在这个范围中单例了,在其他范围中也可能会有多个。范围就是域,就是 Scope)

@Singleton
class CoffeeMaker {
  ...
}

由于 Dagger2 将对象图中的范围实例与 component 组件的实现类对象相关联,那么组件需要声明它所表示的范围。比如,没有必要在同一个 component 上同时标注 @Singleton 和 @RequestScoped 注解,因为这两个注解所代表的域具有不同的生命周期,所以在使用这两个注解时,它们标注的 component 的声明周期也应该是不同的。要想将一个 component 组件与一个给定的域联系起来,很简单,只需要在 component 接口上使用该域(@Singleton 只是一种,还可以是各种自定义注解)注解标注即可。

/*
 * @Singleton 标注了接口CoffeeShop
 * 表示这个 component 与 @Singleton 联系起来
 * 那么在 DripCoffeeModule.class 中被 @Singleton 标注的 provide 方法
 * 就会提供在此范围内的单例对象
 */
@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
  CoffeeMaker maker();
}

component 组件也可能被多个域注解标注。这表示它们(域注解)都是同一范围的别名(实际就代表都是一个域),所以 component 组件就会包含绑定在它上的域(域注解)中的任意一个对象。(@A,@B都标注在一个 DemoComponent 上,那么在 module 中的 @Provides 注解标注的方法(还需要一个域注解标注),标注该方法上的域注解是 @A 还是 @B 都属于同一个范围。都可以通过 DaggerDemoComponent (Dagger2为我们生成的实现类) 获得。)

Reusable scope (可重用的范围)

有时候你想限制被 @Inject 注解标注的类的实例化次数,或者被 @Provides 注解标注的方法的调用次数,但是你不需要保证在任何特定 component 组件 或 subcomponent 子组件的生命周期中使用完全相同的实例对象。这在Android环境中很有用,因为分配的成本可能很高(这个成本指的是?)。

对于这些绑定来说,你可以使用 @Reusable 域。 @Reusable 域注解,与其他的域有一些不同,它不与任何单例的 component 组件产生联系。相反,实际使用绑定的 component 组件会缓存被返回的或实例化的对象(没懂)。

那意味着如果你在 component 组件初始化一个被 @Reusable 绑定的 module 模块,但是实际上只有一个 subcomponent 子组件使用这个绑定,那么只有这个子组件会缓存绑定的对象(这个缓存,指的是持有引用?)。如果两个不共享父级(父接口)的 subcomponent 子组件都使用这个绑定,那么他们中的每一个(就是每一个子组件)都会缓存一份属于自己的对象(子组件A缓存对象a,子组件B缓存对象b,a、b是同一类型的不同对象)。如果一个 component 的父级(父接口)已经缓存好了这个对象,那么 subcomponent 子组件会使用它( subcomponent 会直接使用 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() {}
}

Releasable references (可释放的引用)

反对:此功能已启用,计划在2018年7月删除。
ok,少翻译一大块,开心的不行。
算了还是了解一下吧。

当绑定的时候使用了域注解,那就意味着这个 component 组件对象持有一个绑定对象的引用,直到这个 component 对象被垃圾回收机制回收。在安卓敏感的内存环境中,当内存不足时,你也许想让没有正在使用的域对象被垃圾回收机制处理。

在那种情况下,你可以定义一个域(scope) ,并且使用 @CanReleaseReferences 注解这个 scope 。

@Documented
@Retention(RUNTIME)
//这样,该域中(MyScope)的对象,如果不是正在使用中,当内存不足时就会被释放
@CanReleaseReferences
@Scope
public @interface MyScope {}

当你确定允许当某个域中的对象当前未被其他对象使用时,可以被垃圾回收机制回收,那么你可以向你的域中注入一个 ReleasableReferenceManager 对象,然后调用这个对象的 releaseStrongReferences() 方法,这个方法会使得 component 组件持有一个该对象的 WeakReference 而不是 strong reference。(弱引用:垃圾回收机制每次都会回收掉弱引用的对象。强引用:强引用指向的对象永远不会被垃圾回收机制回收,即时内存不足。)Java强弱软虚四种引用类型

@Inject 
@ForReleasableReferences(MyScope.class)
ReleasableReferenceManager myScopeReferenceManager;

void lowMemory() {
  myScopeReferenceManager.releaseStrongReferences();
}

如果你确定内存不足情景已经过去(就是又有内存了),那么你可以为任意已缓存的在低内存时期调用了 releaseStrongReferences() 后还没有被垃圾回收机制回收的对象恢复成强引用。(这个定语是真的长。。。。)

void highMemory() {
  myScopeReferenceManager.restoreStrongReferences();
}

Lazy injections (懒注入)

有时你需要一个被懒加载的对象。对于任意的绑定 T (泛型开始),你可以创建一个在第一次调用 Lazy<T>'s get() 方法时,才实例化对象的 Lazy<T> 。如果 T 是一个单例的类型,那么 Lazy<T> 会在对象图 ObjectGraph 中需要 T 的任意一个地方注入同一个实例。否则,每一个等待注入的地方都会获得一个它自己的 Lazy<T> 实例(A 中的 Lazy<T> 和 B 中的 Lazy<T> 是同一个类型的不同对象)。不管怎样,对已给定的 Lazy<T> 的后续调用都将返回同样的底层的 T 实例(第一次调用get()时创建T的对象,后续调用get()返回的都是前面创建的同一个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 ( Provider 方式的注入)

有时你需要返回得到多个实例,而不是仅仅注入一个单例的对象。当你有几个选项(工厂,建造者等)时,一个选项要注入一个 Provider<T> 而不是只注入一个 T。每次调用 Provider<T> 的 get() 方法时,Provider 都会调用 T 的绑定逻辑。如果这个绑定逻辑是一个被 @Inject 标注的构造方法,那么再次调用 T 的绑定逻辑就意味着再次调用 T 的构造方法,这时,就创建了一个 T 的新对象,但是如果绑定规则是一个被 @Provides 标注的方法,那么就不能够保证会再次新建一个对象(方法的返回值是我们任意写的,如果我们没写类似 new T() 的代码,那么自然就不会创建新的 T)。

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

译者附加(可能没啥卵用,别喷我):有的时候我们可能会遇到对象的转换,将 List<AAAResponse> 中的数据设置到 List<AAA> 中,可能就会出现上述的循环代码,此时创建 AAA 对象的过程就可以通过多次调用 Provider<AAA> 的 get() 方法来实现。就不用再写 new AAA() 了。当然更好的是注入 Provider<AAASuper> (AAASuper 是 AAA 的超类),这样如果改了需求要将数据转换到 List<BBB> 中,只需要更改注入的对象即可,不需要改动当前类的任何代码。

笔记:注入的 Provider<T> 可能创建了难以理解的代码,也可能是在对象图中设计了错误的范围或错误的对象结构。通常你想使用 factory 或者是 Lazy<T> 或者是更改你代码的生命周期和代码结构,使得你能够直接注入一个 T。 但是,在某些情况下,注入 Provider<T> 可以节省生命(啥意思?)。一个普遍的用途是当你必须使用不符合对象自然生命周期的传统框架时(就用呗就?)。(这个括号也是原文中的内容)(例子:servlets 被设计成单例模式,但是仅仅在 request 的上下文中有效。)

(这里是我加的:) Servlet 是javaweb中处理请求的模块,运行在java客户端的java程序叫做 applet ,为客户端提供支持的叫 server,servlet 就是 server 和 applet 的组合词。在javaweb中,如果不做特殊才处理(设置servlet的级别),只有当请求第一次发生时,才会创建对应的servlet对象,下次再访问同样的请求,不会再创建新的。也就是说如果 request 不发生,则 servlet 便不会做什么事情(正常情况下)。

Qualifiers (限定)

有时单独的类型不足以识别依赖关系。例如,精致的咖啡机在加热不同的物品时应该使用不同的加热器。

在这种情况下,我们添加了一个限定注解。任何一个被 @Qualifier 标注的注解(包含自定义注解),都属于限定注解。这里有一个包含在 javax.inject 中的限定注解 @Named 的声明。

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

你可以创建你自己的限定注解,或者直接使用 @Named 。使用限定注解标注在感兴趣的属性和参数上。类型和限定注解,都会被用来表示依赖项。

class ExpensiveCoffeeMaker {
  //如果是不同类型的依赖项,只需要使用一个 `@Inject` 即可
  //这里依赖了两个同类型的对象,那么只根据类型区分就不满足了,可以使用限定注解做区分
  @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 中的某些依赖项没有被绑定的情况下,依然能够正常运行,那么可以在 module 中添加一个 @BindsOptionalOf 标注的抽象方法:

@BindsOptionalOf 
abstract CoffeeCozy optionalCozy();

这意味着被 @Inject 标注的构造方法、成员变量和被 @Provides 标注的方法可以依赖于 @Optional<CoffeeCozy> 对象。如果在 component 组件中有一个 CoffeeCozy 的绑定, 则将显示 Optional ,如果没有 CoffeeCozy 的绑定,那么 Optional 将不存在。

特殊的,你可以注入如下的任意一个。

  • Optional<CoffeeCozy> (除非 CoffeeCozy 有 @Nullable 绑定)
  • Optional<Provider<CoffeeCozy>>
  • Optional<Lazy<CoffeeCozy>>
  • Optional<Provider<Lazy<CoffeeCozy>>>

(也是原文中的内容:)(你可以注入一个 Provider 或者 Lazy 或者 Provider<Lazy> 不过没啥用)

和下面的一个道理

  • List<String>
  • List<List<String>>
  • List<Hashmap<String,List<String>>>

如果 CoffeeCozy 有绑定,并且绑定是 @Nullable ,那么注入 Optional<CoffeeCozy> 是一个编译期错误,因为 Optional 不能包含 null 。你可以一直注入其他的格式,因为 Provider 和 Lazy 的 get() 方法能够返回 null。

在一个 component 组件中的可选择的隐藏的绑定,如果 subcomponent 子组件包含基础类型的绑定,那么可以在 subcomponent 子组件中显示。

可以使用 Guava’s Optional 或者 Java 8’s Optional。

Binding Instances (绑定实例)

通常在构建 component 时,你会得到可用的数据。例如,假设你有一个使用命令行参数的应用程序,你也许想在你的 component 组件中绑定那些参数。

也许你的应用程序只需要一个参数来表示你想要注入的用户名( @UserName String )。你可以在 component 的 builder 中添加一个 @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])//传入参数,与传入module类似
      .build()
      .app();
  app.run();
}

在上述例子中,在 component 组件中注入 @UserName String 将在调用次方法时,使用提供给 Builder 的实例。在构建 component 组件之前,必须先调用所有的 @BindsInstance 标注的方法,传递非空值(下面 @Nullable 绑定的除外)。

如果一个被 @BindsInstance 标注的方法中的参数被 @Nullable 标注,那么这个绑定就会被认为是可空的,就像 @Provides 标注的方法也是可空的一样,注入点也一定标记了 @Nullable,那么 null 就是该帮绑定的一个可接收的值。此外, Builder 的用户可以不调用这个方法,那么 component 组件会认为这个实例是一个null值。

@BindsInstance 标注的方法应该优先使用构造函数参数编写 @Module 并且立即提供这些值。

Compile-time Validation (编译期验证)

Dagger annotation processor (注解处理器) 是精确的,如果任何一个绑定是无效或者不完整的,那么就会导致编译期错误。例如,下面的 module 被一个没有绑定 Executor 的 component 实例化:

@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.

通过在 component 中的任意一个 module 中添加一个 Executor 的 @Provides 标注的方法(该方法返回一个 Executor 的对象)来修复错误。虽然 @Inject 、 @Module 和 @Provides 注解是单独验证的,但是绑定与绑定之间的关系的验证都发生在 @Component 级别。Dagger 1 严格依赖与 @Module 级验证(可能存在运行时执行反射行为),但是 Dagger 2 不需要这样的验证(以及在 @Module 上附带的配置参数),支持完整的图形验证。

Compile-time Code Generation (编译器的代码生成)

Dagger 的注解处理器也会生成名字类似于 CoffeeMaker_Factory.java 或者 CoffeeMaker_MembersInjector.java 这样的资源文件。这些文件是Dagger实现的细节。你不应该直接使用它们,尽管在进行 debug 调试它们时会很方便。你唯一应该在代码里引用的是那些根据你的 component 生成的前缀为 Dagger 的类型。

Using Dagger In Your Build

你需要在你的应用程序运行时包含 dagger-2.X.jar 。为了触发代码生成器你需要在编译期构建时包含 dagger-compiler-2.X.jar 。查看更多信息 读我 。

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的原理及使用详解 目录 一:Dagger2是什么? 二:为什么要有Dagger2 三:Dagger2如何使用 基本的概念 如何使用Dagger2 高级用法 (1)构造方法需要其他参数时候 (2) 模块之间的依赖关系 (3) @Named注解使用 (4) @Singleton注解 (5)自定义Scoped (6)Subcomponent (7)lazy 和 Provider 四: MVP + Dagger2... 阅读详情

相关推荐

gpops2UsersGuide GPOPS2用户手册

gpops2UsersGuide GPOPS2用户手册

Android开源框架--Dagger2详解

功名只向马上取,真是英雄一丈夫

袁震的博客 4713

gpops2UsersGuide.rar_GPOPS_gpops2userguide_gpops使用方法_gpops手册_最优控

GPOPS&#8722 II:一款求解最优控制问题的软件手册(内含源代码,基于变阶自适应正交配点方法编写的)

Dagger2 的使用与基本原理

使用 Dagger2 的最佳做法:如果有可能,通过@Inject进行构造函数注入,以向 Dagger 图中添加类型。使用@Binds告知 Dagger 接口应采用哪种实现使用@Provides告知 Dagger 如何提供你的项目所不具备的类只能在组件中声明一次模块根据注释的使用生命周期,为作用域注释命名,例如和依赖注入。

tmacfrank 2019

NX二次开发编译时dll自动数字签名及拷贝

在UG5.0开始,所有基于UG二次开发的DLL都要“签名”后才能被客户端上正版的NX调用。

菜鸟爱飞不飞的博客 2683

大学生智能车竞赛初次尝试经验分享--1(硬件绘制电路,pcb部分)

本人机械电子专业大二暑假参加全国大学生智能车竞赛,之前参加过一些其他类型的比赛有一点基础,由于之前疫情影响所以做智能车比赛断代了,没有学长带也没有思路,从一开始的一头雾水逐渐摸索出我感觉比较实用的经验。 一.首先是硬件的准备工作: 1.硬件部分与要自己设计需要的电路制成pcb。推荐使用Altium Designer20,AD比较常用所以有多实用的教学视频,另外需要自己了解布线规则,包括信号线,电源线常用的宽度 一些电源电路,驱动电路建议用multisim仿真,没有问题再进行制版 单片机有关的需要仿真

weixin_52679034的博客 9831

Dagger2的简单使用及基本原理

前言    java注解除了解耦个人认为还有使代码简洁、逻辑更加突出业务逻辑代码、由于创建对象的集中,更加方便对象的维护与测试、对象的创建和生命周期的管控都交由框架等优点。 Dagger2的历史:   Dagger1是Square公司开发,Dagger2则由谷歌接收在dagger1的分支上开发而来,Dagger1用了反射,消耗了一些性能,Dagger2则被谷歌声称性能提高了13%,但是其灵活性减少 Dagger2的使用 添加依赖: implementation 'com.google.dagger:dagg

wangsheng5454的博客 4707

Unet++改进33:添加ConvolutionalGLU(2024最新改进方法)|紧凑的网络结构和高效的运算

由于残差连接的深度退化效应,许多依赖堆叠层进行信息交换的高效视觉变形模型往往不能形成足够的信息混合,导致视觉感知不自然。为了解决这个问题,在本文中,我们提出了聚合注意力,这是一种基于仿生设计的令牌混合器,它模拟生物中央凹视觉和连续眼运动,同时使特征映射上的每个令牌具有全局感知。此外,我们结合了可学习的令牌,与传统的查询和键交互,这进一步多样化了亲和矩阵的生成,而不仅仅依赖于查询和键之间的相似性。我们的方法不依赖于堆叠进行信息交换,从而有效地避免了深度退化,实现了自然的视觉感知。

AIcurator的博客 683

将Android进行到底之Dagger2(一)

当前我们开发软件大多都是使用面向对象的思想。将我们想要操作的对象抽象成类,使用的时候再根据类实例化出我们想要操作的对象,再通过对这些对象的管理和组合操作,最终形成咱们想要的软件。而一个软件是否优秀就取决于对组成软件对各个对象的管理和组合是否合理。各个对象之间的耦合度是否足够低。而这些可以使用dagger2和hilt这类的依赖注入框架很好的实现低耦合的软件系统............

知识应该共享,创造才应收费 2146

1-Wire 单总线硬件结构及软件时序分析(实测波形+C代码分析)

1根线就可以通信,是不是很神奇?因为其节省I/O口的优点而被广泛使用!

记得诚 1万+

FreeRTOS深入教程(任务创建的深入和任务调度机制分析)

本篇文章将带大家深入学习任务的创建和分析任务调度的机制。本篇文章深入的讲解了任务创建的内部实现和任务调度的源代码分析和实现,学习这篇文章有助于更深入的学习FreeRTOS的源码。

m0_49476241的博客 6391

Dagger 2 原理和使用

Dagger2是一个编译时依赖注入框架,通过注解处理器在编译时生成依赖管理代码。其核心机制包括:@Inject标记依赖需求与提供者,@Module/@Provides定义依赖创建逻辑,@Component作为依赖提供与使用的桥梁。Dagger2的优势在于编译时验证依赖关系、无反射开销、代码可追溯性,并支持复杂依赖管理、模块化开发、测试优化等场景。在Android开发中特别适合处理生命周期绑定、作用域管理等平台特性,相比运行时DI框架具有显著的性能优势。

Mark Wu 的博客 1146

10年上位机开发踩过的20个坑:C#通信/线程/UI适配避坑指南

通信层:所有通信操作异步化、异常全覆盖,加断线重连+心跳检测,多设备隔离资源;线程层:UI更新必做跨线程判断,共享数据加锁,线程池限流,异常不静默;UI层:适配高分屏/DPI,用布局容器替代固定坐标,多屏场景绑定主屏幕。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 904

Dagger2 User's Guide(全文翻译--中英)

Dagger User’s Guide(Dagger用户指南) The best classes in any application are the ones that do stuff: the BarcodeDecoder, the KoopaPhysicsEngine, and the AudioStreamer. These classes have dependencies; per...

xkfer 945

“快速掌握Vivado FPGA开发流程“——详解Vivado开发流程及实例

快速掌握Vivado FPGA开发流程”——详解Vivado开发流程及实例FPGA技术在现代电子领域中应用越来越广泛,而Vivado作为业界最流行的FPGA设计工具之一,也受到越来越多人的关注和使用。那么如何快速掌握Vivado FPGA开发流程呢?本文将详细介绍Vivado开发流程,并通过实例来加深您的理解。首先,我们需要了解Vivado开发流程的主要步骤:创建工程、设计电路、约束电路、生成比特流。接下来,让我们一步步了解这些步骤。

2301_78484069的博客 731

Python之abbrobotegm包语法、参数和实际应用案例

`abbrobotegm` 是用于控制 ABB 机器人的 Python 接口包,主要通过 EGM(Externally Guided Motion,外部引导运动)协议与 ABB 机器人控制器进行通信,实现对机器人的实时位置控制、状态监控和运动引导等功能。该包适用于需要将 ABB 机器人与外部系统(如视觉系统、传感器等)集成的场景。

王国平的博客 1254

Dagger2 User's Guide(翻译)

概述依赖注入(dependency injection)是一个对象为另一个对象提供依赖关系的技术手段。简单点说,就是一个对象(client)要依赖其它对象(services)才能完成工作,那么这个对象(client)就对其它对象(services)产生了依赖,而依赖注入就是把依赖(services)在需要的时候自动传给client,而不是client自己创建或者寻找services。也就是说客户对象

商明超的专栏 3520

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Android快速依赖注入框架Dagger2使用1

一、啥是Dagger2前面的概念可能开始看不懂,给点耐心,看到例子就懂了。 本篇文章需要注解方面的知识,不了解的可以先看:http://blog.youkuaiyun.com/niubitianping/article/details/601451281.1 简介Dagger2是一个Android/Java平台上快速依赖注入框架,由谷歌开发,最早的版本Dagger1 由Square公司开发。依赖注入框架主要用于

KeepStudy 6074

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Dagger 2 系列(六) -- 进阶篇:Component 依赖、@SubComponent 与多 Component 下的 Scope 使用限制...

该系列博客的最终目标: 搭建 MVP + Dagger2 框架 该系列博客包含以下几篇内容: Dagger 2 系列(一) -- 前奏篇:依赖注入的基本介绍 Dagger 2 系列(二) -- 基础篇:@Inject、@Component Dagger 2 系列(三) -- 基础篇:@Module 和@Provides Dagger 2 系列(四) -- 基础篇:@Named 和 @Q...

weixin_34348174的博客 450

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Dagger2进阶学习

通过上述的步骤,就可以完成对Activity的注入,但是整体略显繁琐,当我们想要注入某个Activity中,就必须要创建该Activity的Component,而且这些代码都是模板代码,不需要动脑子,另外每次增加一个Component,就要在ActivityModule的Subcomponent集合中添加它。另外还提供注入到Application类中的注入方法inject,值得注意的是,inject方法中的参数为我们自定义的App类型,而非Application类。

2401_83703797的博客 834

Dagger2相关知识

Dagger2依赖注入知识

似霰的博客 1021

Dagger2基本使用与原理

上一篇介绍了Dagger2的一些注解点击打开链接 一、Dagger2的一些简单配置 1.在build.gradle(project..)中添加apt插件 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 2.在build.gradle(Module:app)中添加如下代码,应用apt插件 apply plugin: 'c

fan_zyf的博客 9163

Android 神兵利器Dagger2使用详解(一)基础使用

Android 神兵利器Dagger2使用详解(一)基础篇本系列书写原因:在公司一个新的共同开发项目中,使用到了Dagger2依赖注入,在使用它的时候,因为框架的原因产生了一些问题(代码风格的不同?),发现自己对于Dagger2还是有一些没有理解到位的地方,于是干脆抽个时间搞懂它,从最基础的使用开始,我们一点点从源码深入它,去感受依赖注入可以给代码开发带来怎样的魅力。1 什么是依赖注入依赖注入是一种

却把清梅嗅的博客 2万+

Dagger2从入门到放弃

JvmStatic// 在 Module 中定义常量@JvmStatic// Java 代码调用。

Dengzm94 967

【Dagger2】一文让你从Dagger2入门到熟练

@Inject 注解在属性中表示该属性需要依赖注入,注解的属性不能使用private修饰,只能用默认、protected或public 注解在方法中表示该方法需要依赖注入,注解的方法不能是抽象方法,不能是private修饰的 注解在构造方法中表示此类能为Dagger2提供依赖关系,如果有多个构造函数,只能注解一个,否则会报错 ...

It一zhai男 502

Dagger2

什么是Dagger2 当我们有一个Person类 需要去实例化,通常手段都是Computer computer = new Computer(), 如果 Computer 中需要依赖一个Phone对象,一般情况下都是通过构造传入new Computer(new CPU),或者 set 方法 传入,这其实就是依赖注入最常见的两种方式,Dagger2 就是此两种方式上,通过注解+JavaPoet等手段,在编译期间,动态的生成 依赖方 所需要的对象。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直

xiaoJiu___的博客 403
上一篇: Dagger2错误积累:a binding with matching key exists in component
下一篇: 安卓开发者官网:USB 主机与附件 概述
george_zyf
博客等级 码龄11年 132粉丝 82原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值