news 2026/7/16 3:31:17

依赖注入Dagger初学习

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
依赖注入Dagger初学习







第一章 一堆废话

第01节 前置内容

最近在学习 Android 源码,在研究SystemUI源代码的过程中,发现了一段代码:

publicinterfaceStatusBarIconController...{}protectedStatusBarIconControllermIconController;protectedvoidgetDependencies(){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)

它的核心思想是:对象的依赖(即它所需要的其他对象)不由自身创建或查找,而是由外部容器或框架在运行时“注入”进来

简单来说:“我不要自己找我要的东西,你直接给我。”




哦,这里我看到了几个核心词汇DIIOC

这两个词汇,我可以说并不陌生啊。

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 就像是一家具体的餐厅, 比如 "海底捞" 它实现了 "去餐厅吃饭" 这个理念。

对比一下DaggerSpring IOC的区别

特性DaggerSpring
注入的时机编译时(静态生成代码)运行时(反射)
性能高(无反射)较低(反射有开销)
配置方式注解 + 手动编写 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包)

因为没有gradlemaven相关的内容,我全部采用的是lib jar包,所以前面需要去准备相关的jar

相关 jar 包的下载地址:

maven 中央仓库地址 https://central.sonatype.com/

需要下载的依赖和版本:

编号依赖名称版本号
1annotation-api1.3.0
2auto-common0.1.0
3checker-qual2.5.2
4dagger2.24
5dagger-complier2.24
6dagger-complier-with-poet
7dagger-producers2.24
8dagger-spi2.24
9failureaccess1.0.1
10guava27.1
11javapoet1.11.0
12javax.inject1

为了方便大家快速学习,我已经将使用的 jar 包提供了下载地址。

点我进入jar包的下载地址




第03节 项目鸟瞰

在学习的过程中,在项目文档结构中,踩了不少的坑,防止后续有更多的人,踩坑,将项目的 鸟瞰图,拿出来对照,能够清晰的看明白代码结构。

配置文件studyDagger/studyDagger.iml内容如下:

<?xml version="1.0" encoding="UTF-8"?><moduletype="JAVA_MODULE"version="4"><componentname="NewModuleRootManager"LANGUAGE_LEVEL="JDK_17"inherit-compiler-output="true"><exclude-output/><contenturl="file://$MODULE_DIR$"><sourceFolderurl="file://$MODULE_DIR$/out/production/studyDagger/generated"isTestSource="false"generated="true"/><sourceFolderurl="file://$MODULE_DIR$/src"isTestSource="false"/></content><orderEntrytype="inheritedJdk"/><orderEntrytype="sourceFolder"forTests="false"/><orderEntrytype="library"name="lib"level="project"/></component></module>







第二章 入门案例

第01节 案例代码

汽车工厂接口

packagecom.study;importdagger.Component;@ComponentpublicinterfaceCarFactory{CarBMWgetBMW();CarAudigetAudi();}

宝马汽车类

packagecom.study;importjavax.inject.Inject;publicclassCarBMW{@InjectpublicCarBMW(){}publicStringgetName(){return"宝马汽车";}}

奥迪汽车类

packagecom.study;importjavax.inject.Inject;publicclassCarAudi{@InjectpublicCarAudi(){}publicStringgetName(){return"奥迪汽车";}}

测试类

packagecom.study;publicclassDemo{publicstaticvoidmain(String[]args){CarFactoryfactory=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

packagecom.study;publicinterfaceCar{StringgetName();}

基础子类 宝马汽车

packagecom.study;importjavax.inject.Inject;publicclassCarBMWimplementsCar{@InjectpublicCarBMW(){}publicStringgetName(){return"宝马汽车";}}

基础子类 奥迪汽车

packagecom.study;importjavax.inject.Inject;publicclassCarAudiimplementsCar{@InjectpublicCarAudi(){}publicStringgetName(){return"奥迪汽车";}}

模块管理类 CarModule

packagecom.study;importdagger.Binds;importdagger.Module;importjavax.inject.Qualifier;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;@ModulepublicabstractclassCarModule{@Qualifier@Retention(RetentionPolicy.RUNTIME)public@interfaceBMW{}@Qualifier@Retention(RetentionPolicy.RUNTIME)public@interfaceAudi{}@Binds@BMWpublicabstractCarbindBMW(CarBMWcarBMW);@Binds@AudipublicabstractCarbindAudi(CarAudicarAudi);}

汽车工厂类

packagecom.study;importdagger.Component;@Component(modules=CarModule.class)publicinterfaceCarFactory{@CarModule.BMWCargetBMW();@CarModule.AudiCargetAudi();}

测试类

packagecom.study;publicclassDemo{publicstaticvoidmain(String[]args){CarFactoryfactory=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节 案例代码

车辆品牌的接口

packagecom.study;publicinterfaceCarBrand{StringgetName();}

车辆工厂接口

packagecom.study;importdagger.Component;importjava.util.Map;@Component(modules=CarModule.class)publicinterfaceCarFactory{Map<Class<?>,CarBrand>getCarMap();}

基础子类 奥迪汽车

packagecom.study;importjavax.inject.Inject;publicclassCarAudiimplementsCarBrand{@InjectpublicCarAudi(){}@OverridepublicStringgetName(){return"奥迪汽车";}}

基础子类,宝马汽车

packagecom.study;importjavax.inject.Inject;publicclassCarBMWimplementsCarBrand{@InjectpublicCarBMW(){}publicStringgetName(){return"宝马汽车";}}

模块类

packagecom.study;importdagger.Binds;importdagger.Module;importdagger.multibindings.ClassKey;importdagger.multibindings.IntoMap;@ModulepublicabstractclassCarModule{@Binds@IntoMap@ClassKey(CarBMW.class)publicabstractCarBrandbindCarBrandBMW(CarBMWcarBMW);@Binds@IntoMap@ClassKey(CarAudi.class)publicabstractCarBrandbindCarBrandAudi(CarAudicarAudi);}

测试类

packagecom.study;importjava.util.Map;publicclassDemo{publicstaticvoidmain(String[]args){CarFactoryfactory=DaggerCarFactory.create();Map<Class<?>,CarBrand>carMap=factory.getCarMap();CarBrandbrandAudi=carMap.get(CarAudi.class);CarBrandbrandBMW=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的代码不就是 懒加载的吗!

前面我们说到了,多重绑定 和 懒加载 的实现方式,关于后续的思考,如何操作呢?
怎样代码,才能符合 商用级的标准? 怎样的代码才能符合 工业级标准?

想要了解更多,点击下面,进入我的下一篇文章吧。

点我,进入 依赖注入Dagger懒加载





第五章 注意事项

第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







版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/16 3:28:30

OpenViking+OpenClaw构建多Agent共享记忆中台

1. 项目概述&#xff1a;为什么“失忆”是多Agent系统最隐蔽的致命伤你有没有试过让两个AI助手协同完成一件事&#xff1f;比如让一个查资料、一个写报告&#xff0c;结果后者完全不记得前者刚找到的关键数据&#xff0c;还得重新问一遍——这根本不是协作&#xff0c;是各自为…

作者头像 李华
网站建设 2026/7/16 3:28:02

Windows Defender Remover v13.0:Windows安全组件深度管理解决方案

Windows Defender Remover v13.0&#xff1a;Windows安全组件深度管理解决方案 【免费下载链接】windows-defender-remover A tool which is uses to remove Windows Defender in Windows 8.x, Windows 10 (every version) and Windows 11. 项目地址: https://gitcode.com/gh…

作者头像 李华
网站建设 2026/7/16 3:27:03

ABAP 没有 TemplateRef,但四类 SAP 技术可以承担相近职责

在一个采购审批页面里,订单处于待审批、已批准、已驳回三种状态时,页面中间区域可能呈现完全不同的内容。使用 Angular 时,可以分别声明几个 <ng-template>,再把对应的 TemplateRef 交给某个宿主组件,由宿主决定在什么位置、带着什么上下文创建视图。 把同一个问题…

作者头像 李华
网站建设 2026/7/16 3:26:19

OpenClaw:Windows原生数字员工,中文指令驱动的办公自动化

1. OpenClaw不是“另一个AI工具”&#xff0c;而是Windows上第一个真正能自己干活的数字员工你有没有过这种时刻&#xff1a;早上打开电脑&#xff0c;第一件事是手动整理昨天下载的27个PDF&#xff0c;把它们按项目名归类到不同文件夹&#xff1b;接着复制粘贴三份Excel里的客…

作者头像 李华
网站建设 2026/7/16 3:21:50

股票买卖问题

1、基础问题只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。class Solution {public int maxProfit(int[] prices) {int n prices.length;int pre prices[0]; //记录可买入的最小值int a…

作者头像 李华
网站建设 2026/7/16 3:18:37

中国汽车月度销量预测与市场趋势分析

1 研究背景与分析目标 汽车月度销量受到季节、品牌竞争、产品生命周期和外部环境共同影响。车系级销量预测可为库存计划、生产排期和营销资源配置提供参考。本案例不追求复杂算法&#xff0c;而是强调清晰的数据流程、合理的时间划分和可解释的结果。 2 数据来源与字段说明 …

作者头像 李华