Dagger2 入门解析

Dagger2 入门解析前言在为 dropwizard 选择 DI 框架的时候考虑了很久 Guice 比较成熟 Dagger2 主要用于 Android 虽然都是 google 维护的 但 Dagger2 远比 guice 更新的频率高 再一个是 Dagger2 不同于 guice 的运行时注入 编译时生成代码的做法很好 提前发现问题 更高的效率 作者 Ryan Miao 本文为作者原创 转载请注明出处 http www cnblogs com

Dagger2 入门解析

前言

在为dropwizard选择DI框架的时候考虑了很久。Guice比较成熟,Dagger2主要用于Android。虽然都是google维护的,但Dagger2远比guice更新的频率高。再一个是,Dagger2不同于guice的运行时注入,编译时生成代码的做法很好。提前发现问题,更高的效率。

还是那句话,百度到的dagger2资料看着一大堆,大都表层,而且和Android集成很深。很少有单独讲Dagger2的。不得已,去看官方文档。

HelloWorld

官方的example是基于maven的,由于maven天然结构的约定,compile的插件生成可以和maven集成的很好。而我更喜欢gradle,gradle随意很多,结果就是编译结构需要自己指定。

demo source: https://github.com/Ryan-Miao/l4dagger2

结构如下:

. ├── build.gradle ├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── readme.md ├── settings.gradle └── src └── main ├── java │ └── com │ └── test │ └── l4dagger2 │ └── hello │ ├── CoffeeApp.java │ ├── CoffeeMaker.java │ ├── DripCoffeeModule.java │ ├── ElectricHeater.java │ ├── Heater.java │ ├── Pump.java │ ├── PumpModule.java │ └── Thermosiphon.java ├── resources └── webapp 11 directories, 15 files 

加载依赖

build.gradle

plugins { id "net.ltgt.apt" version "0.12" id "net.ltgt.apt-idea" version "0.12" id "net.ltgt.apt-eclipse" version "0.12" } repositories { mavenLocal() maven { url "http://maven.aliyun.com/nexus/content/groups/public/" } mavenCentral() } group 'com.test' version '1.0-SNAPSHOT' apply plugin: 'java' apply plugin: 'war' apply plugin: 'idea' sourceCompatibility = 1.8 dependencies { compile 'com.google.dagger:dagger:2.12' apt 'com.google.dagger:dagger-compiler:2.12' testCompile group: 'junit', name: 'junit', version: '4.12' } 

Note that
plugins插件需要放到最开头。然后,由于设计编译时生成sourceSet类,针对IDE需要添加对应的插件。
dagger2生成的类放在build/generated/source/apt/main




Coding Time

接下来的内容就和官方的demo一样了。

com.test.l4dagger2.hello.CoffeeApp

public class CoffeeApp { 
    @Singleton @Component(modules = { DripCoffeeModule.class }) public interface CoffeeShop { 
    CoffeeMaker maker(); } public static void main(String[] args) { CoffeeShop coffeeShop = DaggerCoffeeApp_CoffeeShop.builder().build(); coffeeShop.maker().brew(); } } 

com.test.l4dagger2.hello.DripCoffeeModule

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

com.test.l4dagger2.hello.PumpModule

@Module abstract class PumpModule { 
    @Binds abstract Pump providePump(Thermosiphon pump); }

com.test.l4dagger2.hello.Pump

interface Pump { void pump(); }

com.test.l4dagger2.hello.Thermosiphon

class Thermosiphon implements Pump { private final Heater heater; @Inject Thermosiphon(Heater heater) { this.heater = heater; } @Override public void pump() { if (heater.isHot()) { System.out.println("=> => pumping => =>"); } } } 

com.test.l4dagger2.hello.Heater

interface Heater { void on(); void off(); boolean isHot(); }

com.test.l4dagger2.hello.ElectricHeater

class ElectricHeater implements Heater { boolean heating; @Override public void on() { System.out.println("~ ~ ~ heating ~ ~ ~"); this.heating = true; } @Override public void off() { this.heating = false; } @Override public boolean isHot() { return heating; } }

com.test.l4dagger2.hello.CoffeeMaker

 class CoffeeMaker { private final Lazy 
  
    heater; 
   // Create a possibly costly heater only when we use it. 
   private 
   final Pump pump; 
   @Inject CoffeeMaker(Lazy 
   
     heater, Pump pump) { 
    this.heater = heater; 
    this.pump = pump; } 
    public 
    void 
    brew() { heater.get().on(); pump.pump(); System.out.println( 
    " [_]P coffee! [_]P "); heater.get().off(); } } 
    
  

针对DaggerCoffeeApp_CoffeeShop不识别问题,运行编译后就可以了。

sh gradlew build

结果

Run main method

~ ~ ~ heating ~ ~ ~ => => pumping => => [_]P coffee! [_]P 

用法分析

注入原理

编译时扫描注解,生成对应的builder和factory。这点和spring不同,spring是运行时通过反射生成instance。另一个问题就是由于是静态工厂,那么就不能动态绑定了。不过可以通过其他的手段弥补。

以下来自详解Dagger2

  • @Inject: 通常在需要依赖的地方使用这个注解。换句话说,你用它告诉Dagger这个类或者字段需要依赖注入。这样,Dagger就会构造一个这个类的实例并满足他们的依赖。
  • @Module: Modules类里面的方法专门提供依赖,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的 依赖。modules的一个重要特征是它们设计为分区并组合在一起(比如说,在我们的app中可以有多个组成在一起的modules)。
  • @Provide: 在modules中,我们定义的方法是用这个注解,以此来告诉Dagger我们想要构造对象并提供这些依赖。
    @Component: Components从根本上来说就是一个注入器,也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。

  • Components可以提供所有定义了的类型的实例,比如:我们必须用@Component注解一个接口然后列出所有的@Modules组成该组件,如 果缺失了任何一块都会在编译的时候报错。所有的组件都可以通过它的modules知道依赖的范围。
  • @Scope: Scopes可是非常的有用,Dagger2可以通过自定义注解限定注解作用域。后面会演示一个例子,这是一个非常强大的特点,因为就如前面说的一样,没 必要让每个对象都去了解如何管理他们的实例。在scope的例子中,我们用自定义的@PerActivity注解一个类,所以这个对象存活时间就和 activity的一样。简单来说就是我们可以定义所有范围的粒度(@PerFragment, @PerUser, 等等)。
  • Qualifier: 当类的类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示。例如:在Android中,我们会需要不同类型的context,所以我们就可以定义 qualifier注解“@ForApplication”和“@ForActivity”,这样当注入一个context的时候,我们就可以告诉 Dagger我们想要哪种类型的context。

1. 入口

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

dagger中Component就是最顶级的入口,dagger为之生成了工厂类DaggerCoffeeApp_CoffeeShop, 目标是构建CoffeeMaker, 在CoffeeMaker中使用了Injection,那么依赖要由工厂类来提供。工厂类是根据modules的参数来找依赖绑定的。

本例中,指向了DripCoffeeModule,意思是CoffeeMaker的依赖要从这个module里找。

工厂名称生成规则
– 如果Component是接口, 则生成Dagger+接口名
– 如果Component是内部接口,比如本例,则生成Dagger+类名+ _+ 接口名




2. 依赖管理

module看起来似乎和spring里的configuration有点相似,负责声明bean。而且同样支持继承,子module拥有父亲的元素。 这点和spring的context也很像,子context可以从父context里获取instance。对应的Java里的继承也同样,子类可以使用父类的属性和方法。

这里可以把DripCoffeeModule当做父类,而PumpModule为子类。
Dagger2 入门解析

但是, 引用注入的时候却和spring相反,module之间

在spring里,子context拥有所有的bean,所以在子context里可以注入任何bean。而父context只能注入自己声明的bean。

而在dagger2的这个module里,module可以看做是一个打包。最外层的包显然包含了所有的bean。因此,在CoffeeShop中引入的是父module DripCoffeeModule。在子module PumpModule中的Thermosiphon可以注入声明在DripCoffeeModule里的Heater实例。

当然,造成这个问题的原因是生成的时候的顺序有关。调整下顺序,把PumpModule引入Component里,然后,把DripCoffeeModule include到PumpModule里。此时一样没啥问题,只是掉了个。不同的是,父子对调导致Pump变成了父亲的元素,Heater成了子类的元素。然而,一样可以将heater注入到Pump。为啥?等看了源码再了解,这里先搞定用法scop。猜测会不会是在创建Pump的时候发现缺少Heater,然后压栈,去子module里找声明,找到后,弹出栈

Anyway,demo的注入就是这么简单。module起到定义bean的范围的作用, module之间只要连接就是互通的,可以相互注入, 但打包bean还是要靠最外层的module。

3. 具体实现方式

简单的说,就是一个工厂模式,由Dagger负责创建工厂,帮忙生产instance。遵从Java规范JSR 330,可以使用这些注解。现在不研究Dagger2是如何根据注解去生成工厂的,先来看看工厂是什么东西,理解为什么可以实现了DI(Dependency Injection),如何创建IoC(Inverse of Control)容器。

从入口出发。

CoffeeApp.CoffeeShop coffeeShop = DaggerCoffeeApp_CoffeeShop.builder().build(); CoffeeMaker maker = coffeeShop.maker();

DaggerCoffeeApp_CoffeeShop 是生成的工厂类,实现了我们定义Component的接口CoffeeShop.
Dagger2 入门解析

针对Component上的注解

@Singleton @Component(modules = { DripCoffeeModule.class })

首先观察DripCoffeeModule,里面目前声明了一个Provider
, 并且includePumpModule。显然,我们的Component就是由这两个东西决定的。因此,DripCoffeeModule把这两个当做成员变量,这样就有了操纵这两个东西来生成instance的可能。

下一步,就是build()方法了:

public CoffeeApp.CoffeeShop build() { if (dripCoffeeModule == null) { this.dripCoffeeModule = new DripCoffeeModule(); } if (pumpModule == null) { this.pumpModule = new PumpModule(); } return new DaggerCoffeeApp_CoffeeShop(this); }

这里显然就是初始化这两个成员变量。然后创建我们的工厂DaggerCoffeeApp_CoffeeShop

private void initialize(final Builder builder) { this.provideHeaterProvider = DoubleCheck.provider( DripCoffeeModule_ProvideHeaterFactory.create(builder.dripCoffeeModule)); this.pumpModule = builder.pumpModule; }

到这里才开始核心的依赖管理。

initialize分析

先看第一部分,这是关于Heater的。由于Heater声明了Singleton,Dagger通过经典的double-check来实现单例。面试必备。来看看dagger是怎么用的。这里有两种Provider
Dagger2 入门解析
其中,Factory是正宗的工厂。为毛还要专门继承出来一个接口?可以学习下这种抽象方法,虽然Factory和Provider几乎一模一样,但分出来是为了标记。或者说归类。比如,区别于DoubleCheck。看名字都能才出来,DoubleCheck是一个代理类。




虽然简单,但还是有好多可以学习的编程要点。

/ Returns a {@link Provider} that caches the value from the given delegate provider. */ public static 
  
    Provider 
    
    provider(Provider 
    
      delegate) { checkNotNull(delegate); 
     if (delegate 
     instanceof DoubleCheck) { 
     /* This should be a rare case, but if we have a scoped @Binds that delegates to a scoped * binding, we shouldn't cache the value again. */ 
     return delegate; } 
     return 
     new DoubleCheck 
     
       (delegate); } 
      
     
    
  

看看,同样是创建一个新对象,比我们平时多了两步。一是检查Null,我表示遇到最多的生产事故是由NullPointException造成的,然后检查是否需要代理,如果本来就是代理类则直接返回,这里就实现了方法的幂等性,重复调用的结果一致。

接下来看我们的工厂DripCoffeeModule_ProvideHeaterFactory, 真就是一个工厂。但也不能不看,因为这是和我们代码关联最紧密的一步。工厂是如何根据我们的注解生产instance的呢?后面再看。学习源码真心提高抽象思维。

至此,initialize 方法结束。下一步就是生成我们的Component了。

Make instance

public CoffeeMaker maker() { return new CoffeeMaker( DoubleCheck.lazy(provideHeaterProvider), Preconditions.checkNotNull( pumpModule.providePump(new Thermosiphon(provideHeaterProvider.get())), "Cannot return null from a non-@Nullable @Provides method")); }

果然就是直接用构造函数new了一个,因此,不要以为在Component上标记了Singleton就会生产出同一个Component了,每次生产的最外一层的instance,即Component,就是new了一个。但他的依赖就不同了。看看两个依赖的不同生命周期就能明白。

Heater
Heater做了两个处理,一个是Singleton,一个是Lazy, 即懒汉式。Singleton和Lazy是两种设计模式。
Dagger2 入门解析
DoubleCheck实现了Provider和Lazy的接口,而Provider和Lazy除了名字不同以为,一模一样。都是提供一个Get方法。再次体现了接口抽象的命名标记法。






而我们的Heater自然也是集Lazy和Singleton为一体的。这里的CoffeeMaker直接就是一个Lazy,一个代理,暂时不做任何操作。进下一步。

PumpModule
直接调用方法生产数据,因为没有声明为Singleton,则直接new一个就好。其实就是我们平时写的工厂模式的get,不过我们写的时候直接返回一个new值,人家这里帮忙new了,丢进来。没啥大问题。真正的问题又回到了Heater,由于是单例的,必然不能直接new,需要去找持有单例的工厂类拿。而provideHeaterProvider就是前面的DoubleCheck代理。

 private static final Object UNINITIALIZED = new Object(); private volatile Provider 
  
    provider; 
   private 
   volatile Object instance = UNINITIALIZED; 
   private 
   DoubleCheck(Provider 
   
     provider) { 
    assert provider != 
    null; 
    this.provider = provider; } 
    @SuppressWarnings( 
    "unchecked") 
    // cast only happens when result comes from the provider 
    @Override 
    public T 
    get() { Object result = instance; 
    if (result == UNINITIALIZED) { 
    synchronized ( 
    this) { result = instance; 
    if (result == UNINITIALIZED) { result = provider.get(); 
    /* Get the current instance and test to see if the call to provider.get() has resulted * in a recursive call. If it returns the same instance, we'll allow it, but if the * instances differ, throw. */ Object currentInstance = instance; 
    if (currentInstance != UNINITIALIZED && currentInstance != result) { 
    throw 
    new IllegalStateException( 
    "Scoped provider was invoked recursively returning " + 
    "different results: " + currentInstance + 
    " & " + result + 
    ". This is likely " + 
    "due to a circular dependency."); } instance = result; 
    /* Null out the reference to the provider. We are never going to need it again, so we * can make it eligible for GC. */ provider = 
    null; } } } 
    return (T) result; } 
    
  

经典的双重检查实现了懒汉单例模式。值得学习的是,这里并没有将null当做初始值,而是给了一个Object。然后把真正的生产数据的功能抽象,提出来称为Provider。这个Provider就是前面提到的真正干事情的工厂DripCoffeeModule_ProvideHeaterFactory。负责new一个instance出来。然后,值得学习的地方来了。因为单例模式已经不再需要工厂了,那么这个工厂类可以回收了。我们自己的编程习惯是扔着不管,请保姆(垃圾收集器)来干活。这里直接设置为null,值得注意,虽然大家都懂但不一定都会这样写。

至此,全部分析结束。生成的代码不复杂,但抽象度极高,虽然看的容易,但想象出并设计成这样就很难了。百度里一堆自己实现一个DI啥的,说起来简单,DI就是一个工厂模式。但你设计的DI有考虑这么多东西吗。如果没有这么高度的抽象,你如何才能少量的代码实现如此众多高效的功能?是时候学习源码了。

Lazy and Singleton

上面的例子,使用DoubleCheck实现了单例模式的懒汉式。同时,又是懒加载Lazy。让人以为,Lazy和Singleton是一回事。但并不是这样。Lazy的javac注释中有:

Note that each injected {@code Lazy} is independent, and remembers its value in isolation of other {@code Lazy} instances.

Lazy是一种延迟加载手段,其实就是在真实instance外面增加了一层包裹,只有当需要调用的时候才会启用get方法创建一个instance。而DoubleCheck同时继承了Provider和Lazy,因此看着像是单例和延迟加载同体了。

4. SubComponent

事实上,到这里dagger的用法对于服务端来说已经足够了。通过module的连接特性可以定义IoC容器范围,再结合dropwizard,就和springboot一样了。然而,毕竟dagger2是为了Android而打造的,为了适应其复杂的继承体系和生命周期的限制,dagger提供了SubComponent模型。也就是子组件。

刚看到这里会好奇,module已经可以把bean提供出来注入了,为啥还需要子组件?

我并没有真实的在生产环境中使用过dagger,全部认知也就来自对官方文档里的理解。对于Subcomponent的作用,大概有两点: 1)继承扩展功能并绑定生命周期,2)封装。

继承体现在subcomponent可以使用parent的module,共享其生命周期。

封装则是因为但其他人都不可以使用subcomponent的依赖,只能使用subcomponent本身。也就是parent里的Component不能调用subcomponent里的module。

暂时没能理解subcomponent和scope的使用,感觉有些复杂。将在项目中简单使用Module,因为期待得到的DI是最小侵入性的提供inject功能,而考虑这些层次关系以及作用范围,会导致耦合性增强,偏离了最初引入DI的意愿。目前掌握:我需要一个instance,dagger给一个instance给我injec。不需要考虑任何其他问题。

用法总结

  • @Component用来标注Component,最外层,the bean could only be exposed
  • @Module负责管理依赖
  • 使用@Provides可以提供instance,当无法自动绑定的时候,比如接口和实现类
  • 使用@Inject可以让IoC容器负责生成instance,如果没有这个注解,dagger将不认识,当做普通类,无法代理
  • 在使用@Component的时候必须要提供scope范围,标准范围是@Singleton
  • @Component在使用@Module的时候必须匹配相同的scope
  • 通过@Component.modules或者@Module.includes 可以把依赖连接成一个图,可以互相inject
  • 能使用Singleton的时候,要注意标注,否则默认多例

命名规约

  • @Provides方法用provide前缀命名
  • @Module 用Module后缀命名
  • @Component 以Component作为后缀

参考

  • http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0519/2892.html
  • http://www.cnblogs.com/tiantianbyconan/p/5095426.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/227367.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午9:28
下一篇 2026年3月16日 下午9:29


相关推荐

  • Sublime Text 3 全程详细图文教程(转载)

    Sublime Text 3 全程详细图文教程(转载)今天被群里大佬安利了一款文本编辑软件,找了一下相关教程。一、 前言使用SublimeText也有几个年头了,版本也从2升级到3了,但犹如寒天饮冰水,冷暖尽自知。最初也是不知道从何下手

    2022年7月4日
    22
  • 控件之combox

    控件之combox一 combox 显示 nbsp nbsp 首先 combox 有两个属性来存储数据 DisplayMembe 显示成员 ValueMember 值成员 DisplayMembe 是我们在 combox 界面上看到的 ValueMember 是隐藏的数据 一般来说我们只需要设置 DisplayMembe 属性的值即可 循环赋值 通过 combox Items Add 方法绑定数据 给 combox Da

    2026年3月19日
    2
  • ARM汇编循环语句框架、数组求和

    ARM汇编循环语句框架、数组求和ARM 汇编数组求和 ARM 汇编语句循环框架

    2026年3月16日
    2
  • JAVA大数据后台管理系统

    JAVA大数据后台管理系统一款Java语言基于SpringBoot2.x、Layui、Thymeleaf、MybatisPlus、Shiro、MySQL等框架精心打造的一款模块化、插件化、高性能的前后端分离架构敏捷开发框架,可用于快速搭建前后端分离后台管理系统,本着简化开发、提升开发效率的初衷,自研了一套个性化的组件,实现了可插拔的组件式开发方式:单图上传、多图上传、下拉选择、开关按钮、单选按钮、多选按钮、图片裁剪等

    2022年5月4日
    59
  • python检测端口是否被占用_怎么查看端口占用情况

    python检测端口是否被占用_怎么查看端口占用情况开始 gt 运行 gt cmd 或者是 window R 组合键 调出命令窗口 输入命令 netstat ano 列出所有端口的情况 在列表中我们观察被占用的端口 比如是 49157 首先找到它 查看被占用端口对应的 PID 输入命令 netstat aon findstr 49157 回车 记下最后一位数字 即 PID 这里是 2720 继续输入 tasklist findstr 2

    2026年3月18日
    2
  • matlab 矩阵除法

    matlab 矩阵除法Matlab提供了两种除法运算:左除(/)和右除(/)。一般情况下,x=a/b是方程a*x=b的解,而x=b/a是方程x*a=b的解。例:a=[1  2  3;4  2  6;7  4  9]b=[4;1;2];x=a/b则显示:x=      -1.5000        2.0000        0.5000如果a为非奇异矩阵,则a/b和b/a可通过a的逆矩阵与

    2022年6月29日
    85

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号