第一章 一堆废话
第01节 前置内容
最近在学习 Android 源码,在研究 SystemUI 源代码的过程中,发现了一段代码:
java
public interface StatusBarIconController ... {
}
protected StatusBarIconController mIconController;
protected void getDependencies(){
mIconController = Dependency.get(StatusBarIconController.class);
}
// 后面发现存在大量的 Dependency.get(XXXX.class) 的操作!
我当时特别好奇,这个 Dependency.get(XXXX.class) 在源代码当中,大量的出现,他是啥??
通过 AI 搜索,我发现了 他给我的解释是 Dependency 他是 Android 系统服务内部的一种轻量级依赖注入/服务定位器实现,用于解耦、简化构造、支持测试。
它虽然不如 Dagger 强大,但在 AOSP 这种对性能和启动速度敏感的场景下,足够高效且易于理解。
我发现这样的解释,说的有点不够清晰,不明白。
因为这里又有一些新的概念。依赖注入?
依赖注入(Dependency Injection,简称 DI) 是一种软件设计模式,用于实现控制反转(Inversion of Control,IoC)。
它的核心思想是:对象的依赖(即它所需要的其他对象)不由自身创建或查找,而是由外部容器或框架在运行时"注入"进来。
简单来说:"我不要自己找我要的东西,你直接给我。"
哦,这里我看到了几个核心词汇 DI 和 IOC
这两个词汇,我可以说并不陌生啊。
DI: 依赖注入,一种软件设计模式。
IOC: 了解过 Java Spring 框架的应该知道两大核心 IOC 和 AOP
了解过 IOC 的同学,应该清楚知道。
在 Java 当中,一般的写法创建对象可以写为
Car mCar = new Car();
这种是我们常规的写法,学过多态就知道,左右两侧是可以拆分的。
Car mCar1 = new CarBMW(); // 创建了一辆宝马汽车
Car mCar2 = new CarAudi(); // 创建了一辆奥迪汽车
注意:等号左边可以看成是固定的,等号右边 由于 Java 多态的关系,我们可以看成不同的 Car 的创建方式。
如果将 new 的操作,我们自己最初的时候,并不去做,而是交给别人去做,别人做好了,创建好了,我只是拿过来使用 对象的内容!
这种不就是 IOC 的思想吗?
举个 生活中的例子
| 自己租房(传统方式) | 入住酒店(IOC) | |
|---|---|---|
| 控制权 | 我们需要自己找房源、签合同、交水电费、维修家具 | 我们只需要预定房间,酒店负责清洁、维修、换床单 |
| 依赖管理 | 我们需要自己管理水电、网络、家具等所有的依赖 | 酒店系统统一管理所有的服务 |
| 灵活性 | 我们可以随意的改造房间,但是承担所有责任 | 我们无法改变房间的布局,但是享受一站式服务 |
IOC的体现: 我们不再控制 "如何维护居住的环境",而是将控制权交给酒店(容器)。我只需要提出需求(入住),酒店自动提供所有的配套服务。
当然,我这里不是去学习 IOC 的内容,只是去理解 IOC 的设计方式,整体来说: IOC 就是一种 DI 依赖注入的方式。
那么和我们今天探讨的话题,Dagger 有什么关系呢?
回到主题来说:
IOC 控制反转,他是一种设计原则,而 Dagger 是一种具体的实现框架。
简单举例:
IOC 就像 "去餐厅吃饭" 这个概念。(我们不再需要自己做饭了)
Dagger 就像是一家具体的餐厅, 比如 "海底捞" 它实现了 "去餐厅吃饭" 这个理念。
对比一下 Dagger 和 Spring IOC 的区别
| 特性 | Dagger | Spring |
|---|---|---|
| 注入的时机 | 编译时(静态生成代码) | 运行时(反射) |
| 性能 | 高(无反射) | 较低(反射有开销) |
| 配置方式 | 注解 + 手动编写 Module | 注解 + XML / Java Config / 自动扫描 |
| 适用平台 | Android 为主,也可以用于 Java 企业级 | Java 后端 |
| 灵活性 | 较严格,需要显式声明所有依赖 | 更灵活,支持自动装载,懒加载等 |
通过上面的介绍,我们最终确定几点内容
1、Dagger 是一种 DI 依赖注入的方式
2、Dagger 主要应用在 Android 上
3、Dagger 性能高
4、Dagger 注入在编译时,会生成代码
第02节 准备工作
Dagger 我想要从一个最简单的 JavaSE 项目入手。
作为一个最简单的 JavaSE 项目,那么应该做哪些操作呢? (这里不存在 maven 依赖,有的仅有 JavaSE 代码 和 lib jar 包)
因为没有 gradle 和 maven 相关的内容,我全部采用的是 lib jar 包,所以前面需要去准备相关的 jar 包
相关 jar 包的下载地址:
maven 中央仓库地址 https://central.sonatype.com/
需要下载的依赖和版本:
| 编号 | 依赖名称 | 版本号 |
|---|---|---|
| 1 | annotation-api | 1.3.0 |
| 2 | auto-common | 0.1.0 |
| 3 | checker-qual | 2.5.2 |
| 4 | dagger | 2.24 |
| 5 | dagger-complier | 2.24 |
| 6 | dagger-complier-with-poet | |
| 7 | dagger-producers | 2.24 |
| 8 | dagger-spi | 2.24 |
| 9 | failureaccess | 1.0.1 |
| 10 | guava | 27.1 |
| 11 | javapoet | 1.11.0 |
| 12 | javax.inject | 1 |
第03节 项目鸟瞰

在学习的过程中,在项目文档结构中,踩了不少的坑,防止后续有更多的人,踩坑,将项目的 鸟瞰图,拿出来对照,能够清晰的看明白代码结构。
配置文件 studyDagger/studyDagger.iml 内容如下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_17" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/out/production/studyDagger/generated" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="lib" level="project" />
</component>
</module>
第二章 入门案例
第01节 案例代码
汽车工厂接口
java
package com.study;
import dagger.Component;
@Component
public interface CarFactory {
CarBMW getBMW();
CarAudi getAudi();
}
宝马汽车类
java
package com.study;
import javax.inject.Inject;
public class CarBMW {
@Inject
public CarBMW(){
}
public String getName() {
return "宝马汽车";
}
}
奥迪汽车类
java
package com.study;
import javax.inject.Inject;
public class CarAudi {
@Inject
public CarAudi(){
}
public String getName() {
return "奥迪汽车";
}
}
测试类
java
package com.study;
public class Demo {
public static void main(String[] args) {
CarFactory factory = DaggerCarFactory.create();
System.out.println("success: " + factory.getBMW().getName()); // success: 宝马汽车
System.out.println("success: " + factory.getAudi().getName()); // success: 奥迪汽车
}
}
第02节 流程图解
编译时的图解

运行时图解

第03节 总结思考
总结:
1、你看,在 Demo 的运行过程中, 并没有出现 new 的代码。
2、过程说明:
A. Demo 想要车, 那么问 Factory 要!
B. Factory 怎么知道 如何造车的呢?
C. 我们在 Car 的构造方法上, 贴上了 @Inject 标签
D. 承上启下的纽带就是 Dagger 帮我们自动生成的 DaggerCarFactory
因此整个过程中, 我们并没有写一行的 new 代码, 但是车子却被 稳稳当当的造出来了!
思考:
上面代码当中,仅仅是作为一个入门级的案例,目的是为了我们方便去理解代码运行过程。
如果,将它作为工业级项目,他存在一些致命的问题。
致命缺陷: 不符合"面向接口编程"原则
1、问题所在:
CarFactory 直接返回了具体的实现类 CarBMW 和 CarAudi
如果未来有 100种车型, 工厂接口就要写 100个方法, 并且调用方 Demo 必须依赖这些具体的类, 违背了低耦合的设计原则。
2、拓展方向: 接口多实现与多态
A. 解决方案: 引入一个统一的 Car 接口, 让 CarBMW 和 CarAudi 去实现它
B. 在 Dagger 当中存在知识点:
学习使用 @Mudule 与 @Binds (或者 @Provides) 来告诉 Dagger
当别人管你要 Car 接口时, 我们应该实例化哪一个具体的实现类
第三章 基础用法
第01节 案例代码
统一的接口 Car
java
package com.study;
public interface Car {
String getName();
}
基础子类 宝马汽车
java
package com.study;
import javax.inject.Inject;
public class CarBMW implements Car {
@Inject
public CarBMW() {
}
public String getName() {
return "宝马汽车";
}
}
基础子类 奥迪汽车
java
package com.study;
import javax.inject.Inject;
public class CarAudi implements Car {
@Inject
public CarAudi() {
}
public String getName() {
return "奥迪汽车";
}
}
模块管理类 CarModule
java
package com.study;
import dagger.Binds;
import dagger.Module;
import javax.inject.Qualifier;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Module
public abstract class CarModule {
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface BMW {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Audi {
}
@Binds
@BMW
public abstract Car bindBMW(CarBMW carBMW);
@Binds
@Audi
public abstract Car bindAudi(CarAudi carAudi);
}
汽车工厂类
java
package com.study;
import dagger.Component;
@Component(modules = CarModule.class)
public interface CarFactory {
@CarModule.BMW
Car getBMW();
@CarModule.Audi
Car getAudi();
}
测试类
java
package com.study;
public class Demo {
public static void main(String[] args) {
CarFactory factory = DaggerCarFactory.create();
System.out.println("success: " + factory.getBMW().getName()); // success: 宝马汽车
System.out.println("success: " + factory.getAudi().getName()); // success: 奥迪汽车
}
}
第02节 总结思考
总结
上面的这套基础用法,是 Dagger 解决 "同一个接口, 有多个实现类" 的时候, 经典做法。
基于 @Qualifier 自定义注解的显示绑定
优点
1、强制类型安全与编译期检查
每个类都有明确的自定义 注解(如 @BMW @Audi)
在 Component 接口也是显式声明的方法(如: getBMW)
如果配置错误, 编译期间, 直接报错, 绝对不会把奥迪错当成为宝马
2、直观易读
对于使用者来说, API 语义非常清晰
调用 factory.getBMW() 就可以明确知道拿到的是什么对象
缺点 存在的痛点!
1、样板代码严重
每增加一个汽车品牌(如 奔驰 CarBenz)
我们必须成套的增加下面的内容:
A. 一个新自定义的 @Qualifier 注解
B. 在 CarModule 当中添加一条 @Binds 抽象方法
C. 在 CarFactory 接口当中增加一个显式获取的方法 (如: getBenz())
2、违反开闭原则(OCP)
核心工厂接口 CarFactory 被迫感知了所有的具体实现。
一旦需要支持新的车型,就必须修改 CarFactory 接口,
这种操作, 在大型项目或者插件化架构中是不可以接受的。
3、无法动态, 动态获取
所有的调用都是硬编码, 如果业务上需要根据用户输入的字符串 或者类名称 动态获取对应的汽车实例。
基础用法的代码, 很难优雅的做到, 通常需要写一堆的 if...else... 或者 switch....
如何解决上述缺点?
为了解决上述的问题:
每增加一个类, 都需要改动多处接口, 以及 无法动态拓展的 痛点!
我们需要一种 能够自动手机实现类, 并且支持动态路由的机制。
这种方案正是 Dagger 的 Multibindings 多重绑定 派上用场的地方。
第四章 多重绑定
第01节 案例代码
车辆品牌的接口
java
package com.study;
public interface CarBrand {
String getName();
}
车辆工厂接口
java
package com.study;
import dagger.Component;
import java.util.Map;
@Component(modules = CarModule.class)
public interface CarFactory {
Map<Class<?>, CarBrand> getCarMap();
}
基础子类 奥迪汽车
java
package com.study;
import javax.inject.Inject;
public class CarAudi implements CarBrand {
@Inject
public CarAudi(){
}
@Override
public String getName() {
return "奥迪汽车";
}
}
基础子类,宝马汽车
java
package com.study;
import javax.inject.Inject;
public class CarBMW implements CarBrand {
@Inject
public CarBMW() {
}
public String getName() {
return "宝马汽车";
}
}
模块类
java
package com.study;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
@Module
public abstract class CarModule {
@Binds
@IntoMap
@ClassKey(CarBMW.class)
public abstract CarBrand bindCarBrandBMW(CarBMW carBMW);
@Binds
@IntoMap
@ClassKey(CarAudi.class)
public abstract CarBrand bindCarBrandAudi(CarAudi carAudi);
}
测试类
java
package com.study;
import java.util.Map;
public class Demo {
public static void main(String[] args) {
CarFactory factory = DaggerCarFactory.create();
Map<Class<?>, CarBrand> carMap = factory.getCarMap();
CarBrand brandAudi = carMap.get(CarAudi.class);
CarBrand brandBMW = carMap.get(CarBMW.class);
System.out.println("success: " + brandAudi.getName()); // success: 奥迪汽车
System.out.println("success: " + brandBMW.getName()); // success: 宝马汽车
}
}
第02节 总结思考
总结
1、极佳的架构扩展性(符合开闭原则 OCP)
A. Component 接口彻底解耦:
CarFactory 只需要暴露一个统一的接口 getCarMap()
B. 新增品牌零修改:
当需要增加奔驰(CarBenz)时,完全不需要修改 CarFactory 接口。
你只需要写好 CarBenz 类,并在 CarModule 中增加一个 @IntoMap 的绑定方法即可。
这极大地保护了核心架构的稳定性
2、支持动态路由与运行时查找
A. 在实际业务中,我们经常需要根据配置、数据库数据、或者用户的输入来决定实例化哪辆车。
B. 使用 Map 结构,你可以直接通过传递变量(如 Class<?>、或是将 Key 改为 String 后的字符串标识)来动态获取对应的实例。
这在第一套方案(每个品牌对应一个硬编码方法)中是无法直接做到的。
C. 告别自定义注解的样板代码
基础用法中,每加一个品牌都要手写一个 @Qualifier 注解(如 @BMW、@Audi)。
多重绑定方案直接利用了 Dagger 内置的 @ClassKey(或 @StringKey),省去了大量自定义注解的声明,代码结构更加干净整洁。
思考 虽然多重绑定非常优雅,但在特定场景下它也有一些不可忽视的"隐患":
1、丧失了编译期的"强类型安全"约束
A. 必须通过 carMap.get(CarAudi.class) 来获取实例。
B. 如果某个品牌忘记在 Module 中注册(或者拼写错了 Key),编译期不会报错,但在运行期 carMap.get(...) 会直接返回 null。
C. 如果你没有做非空校验(如 brand.getName()),就会触发 NullPointerException(空指针异常)
2、存在不必要的内存与性能开销(非懒加载问题)
A. 即时初始化:
默认情况下,当你调用 factory.getCarMap() 时,
Dagger 会一次性将 Map 里的所有汽车实例全部初始化(包括奥迪、宝马以及未来可能加入的几十个品牌)
B. 性能隐患:
如果某些汽车类的初始化非常消耗资源(比如要连接数据库或加载大文件),
而你其实只需要使用其中一种,这种"全量初始化"就会造成严重的内存和性能浪费。
C. 解决方案:
在实际开发中,通常需要将 Map 的 Value 类型定义为双子(Provider)或懒加载(Lazy),
例如:Map<Class<?>, Provider<CarBrand>>,从而实现用哪个才初始化哪个。
说到了懒加载的方式,我们回头看看, SystemUI 的代码不就是 懒加载的吗!
第五章 注意事项
第01节 编译的依赖

因为在编译期间,需要找到相关的配置
主要位置:
Settings ---> Build, Execution, Deployment ---> Compiler ---> Annoation Processors
1、注意需要配置 lib 文件夹下面的所有 jar 包
2、可选项: Use --processor--module-path compiler option (for Java 9 and later)
3、操作完毕之后, 点击 OK
第02节 生成的out
这里生成的文件,会出现在 out 目录下面,需要对文件,作为 Generated Sources Root

第03节 重新编译
每次修改完毕之后,建议重新编译一下代码,编译代码的方式是
在工具栏,找到 Build ---> Rebuild Project
