- 后端
- 代码生成
- 依赖注入
【免费下载链接】dagger
A fast dependency injector for Android and Java.
导读
本文以 Dagger 官方示例Coffee Maker(Gradle 版)为切入点,讲解如何用一套最小的 Gradle 工程完整落地 Dagger 的核心依赖注入机制。通过阅读与运行 examples/gradle/coffee 中的代码,你将掌握@Inject构造器注入、@Binds接口绑定、@Component组件定义、Lazy<T>延迟注入与@Singleton单例作用域,并能在 IDE 与命令行两种环境下直接跑出 "一杯咖啡"。
示例概述:用一杯咖啡演示依赖注入
该工程是 Dagger 官方 Basic Usage Guide(基本使用指南)中 Coffee Maker 教程的独立 Gradle 实现,位于仓库的examples/gradle/coffee目录。它以冲煮咖啡为业务场景,演示了五个 Dagger 核心概念:
@Inject注解的构造器:让 Dagger 自动发现并创建对象;@Module接口中的@Binds绑定:把抽象类型绑定到具体实现;@Component组件定义:把零散的绑定组装成可使用的注入入口;Lazy<Heater>延迟注入:按需创建代价较高的对象;@Singleton单例作用域:让同一组件内共享同一个实例。
. ├── build.gradle ├── settings.gradle ├── src/ │ └── main/ │ └── java/ │ └── example/ │ ├── common/ │ │ ├── CoffeeLogger.java # Singleton logger │ │ ├── CoffeeMaker.java # Coffee maker brewing logic │ │ ├── ElectricHeater.java # Electric heater implementation │ │ ├── Heater.java # Heater interface │ │ ├── Pump.java # Pump interface │ │ └── Thermosiphon.java # Thermosiphon pump implementation │ └── dagger/ │ ├── CoffeeApp.java # Entry point and @Component definition │ ├── HeaterModule.java # Heater bindings module │ └── PumpModule.java # Pump bindings module工程将代码分成两个包:example.common存放业务类(加热器、水泵、咖啡机、日志器),example.dagger存放 Dagger 相关的绑定模块与组件入口。这种"业务与 DI 配置分离"的划分,正是 Dagger 推荐的组织方式——业务类不感知 DI 框架,只通过注解声明依赖需求。
构建配置:Gradle 如何接入 Dagger
工程使用 Gradle 作为构建系统,根配置在 examples/gradle/coffee/build.gradle,核心依赖声明如下:
plugins { id 'java' id 'application' } repositories { mavenCentral() } java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } dependencies { implementation 'com.google.dagger:dagger:2.60.1' annotationProcessor 'com.google.dagger:dagger-compiler:2.60.1' } application { mainClass = 'example.dagger.CoffeeApp' }关键配置点
| 配置 | 作用 |
|---|---|
implementation 'com.google.dagger:dagger:2.60.1' | 运行时 API,提供@Inject、@Component、@Module、Lazy等注解与工具类(对应仓库 dagger-runtime 模块) |
annotationProcessor 'com.google.dagger:dagger-compiler:2.60.1' | 注解处理器,在编译期解析绑定图并生成DaggerXxx实现类(对应仓库 dagger-compiler 模块) |
id 'application'+mainClass | 声明可执行入口为example.dagger.CoffeeApp,使gradle run可直接运行 |
JavaLanguageVersion.of(17) | 指定 Java 工具链版本为 17 |
两个依赖缺一不可:dagger提供运行所需的注解 API,dagger-compiler负责编译期代码生成。Dagger 的核心机制正是"编译期注解处理"——dagger-compiler在编译@Component接口时生成DaggerCoffeeApp_CoffeeShop实现类,这在后面的运行环节可以看到。
另外,settings.gradle 中声明了工程名rootProject.name = 'dagger-coffee-example';gradle/wrapper/gradle-wrapper.properties 则锁定了 Gradle Wrapper 使用 Gradle 9.3.0 发行版,保证不同环境下构建行为一致。
业务类:从@Inject构造器开始
可注入的构造器
Dagger 注入的起点是@Inject标注的构造器。以日志器为例(CoffeeLogger.java):
@Singleton public final class CoffeeLogger { private final List<String> logs = new ArrayList<>(); @Inject CoffeeLogger() {} public void log(String msg) { logs.add(msg); } public List<String> logs() { return new ArrayList<>(logs); } }CoffeeLogger的无参构造器标注了@Inject,Dagger 因而知道如何创建它。注意该构造器是包级可见的(无public),这符合 Dagger 的推荐实践:可注入对象尽量收紧可见性,由框架负责实例化。
其他业务类同样通过@Inject构造器表达依赖:
- ElectricHeater(ElectricHeater.java):
@Inject ElectricHeater(CoffeeLogger logger),实现Heater接口,on()时记录~ ~ ~ heating ~ ~ ~,通过heating布尔字段维护热态; - Thermosiphon(Thermosiphon.java):
@Inject Thermosiphon(CoffeeLogger logger, Heater heater),实现Pump接口,pump()仅在heater.isHot()为真时记录=> => pumping => =>; - CoffeeMaker(CoffeeMaker.java):
@Inject CoffeeMaker(CoffeeLogger logger, Lazy<Heater> heater, Pump pump),冲煮逻辑为"开火 → 泵水 → 出杯 → 关火"。
Lazy<Heater>:延迟且仅创建一次
CoffeeMaker的构造器引入了一个关键类型——Lazy<Heater>,而不是直接的Heater:
private final Lazy<Heater> heater; // Create a possibly costly heater only when we use it. @Inject CoffeeMaker(CoffeeLogger logger, Lazy<Heater> heater, Pump pump) { this.logger = logger; this.heater = heater; this.pump = pump; } public void brew() { heater.get().on(); pump.pump(); logger.log(" [_]P coffee! [_]P "); heater.get().off(); }Lazy<T>来自 Dagger 运行时 API(见 dagger-runtime 模块下的 Lazy.java),它的语义是:在第一次调用get()时才真正创建对象,且该对象只会创建一次。对CoffeeMaker来说,加热器是"可能创建代价较高"的对象,注入Lazy<Heater>后,brew()中两次调用heater.get()拿到的是同一个实例——第一次get()触发创建并缓存,第二次直接复用,同时避免了在构造CoffeeMaker时提前实例化加热器。
这正是 Dagger 对"昂贵对象"的经典解法:用Lazy把创建时机从注入时刻推迟到使用时刻。
接口绑定:@Module与@Binds
业务依赖中存在两个抽象接口:Heater(Heater.java)与Pump(Pump.java)。接口本身无法被直接实例化,Dagger 需要通过@Module中的@Binds方法把接口绑定到具体实现。
HeaterModule(HeaterModule.java):
@Module interface HeaterModule { @Binds @Singleton Heater bindHeater(ElectricHeater impl); }PumpModule(PumpModule.java):
@Module abstract class PumpModule { @Binds abstract Pump providePump(Thermosiphon pump); }@Binds的使用要点
@Binds方法只接受一个参数,参数类型是实现类,返回类型是被绑定的接口,方法本身没有方法体(接口方法或abstract方法);- 它告诉 Dagger:
Heater由ElectricHeater提供、Pump由Thermosiphon提供,是"接口 → 实现"的最轻量绑定方式; @Singleton可以叠加在@Binds方法上(如bindHeater),使绑定出来的Heater在组件内成为单例——这与ElectricHeater自身@Singleton的写法是等价的,二者取其一即可;@Module可以标注在interface或abstract class上,只要@Binds方法是抽象方法即可。
Thermosiphon构造器本身依赖Heater,因此Pump的绑定链会进一步引用Heater的绑定——依赖图由此层层展开,这也是 Dagger 编译器在编译期需要完整解析整个绑定图的原因。
组件组装:@Component与CoffeeShop
CoffeeApp(CoffeeApp.java)是整个程序的入口,也是@Component的定义处:
public class CoffeeApp { @Singleton @Component(modules = {HeaterModule.class, PumpModule.class}) public interface CoffeeShop { CoffeeMaker maker(); CoffeeLogger logger(); } public static void main(String[] args) { CoffeeShop coffeeShop = DaggerCoffeeApp_CoffeeShop.builder().build(); coffeeShop.maker().brew(); coffeeShop.logger().logs().forEach(log -> System.out.println(log)); } }组件定义三要素
@Component(modules = {...}):声明组件需要哪些绑定模块。本例只有HeaterModule与PumpModule;CoffeeLogger、ElectricHeater、Thermosiphon、CoffeeMaker等由@Inject构造器自动提供,无需模块声明;- 组件方法:
CoffeeShop接口里的无参方法就是"入口点"(entry points),Dagger 据此生成对应的提供方法——maker()返回CoffeeMaker,logger()返回CoffeeLogger; @Singleton:同时标注在组件与相关绑定上,声明整个组件的生命周期内共享实例。本例中CoffeeShop是单例组件,CoffeeLogger、Heater等单例绑定在组件内只有一个实例。
生成的实现类:DaggerCoffeeApp_CoffeeShop
main()中DaggerCoffeeApp_CoffeeShop.builder().build()是关键的一行。DaggerCoffeeApp_CoffeeShop这个名字不是手写的——它由dagger-compiler注解处理器在编译期根据@Component接口自动生成(命名规则为:Dagger+ 组件所在类名 +_+ 组件名)。从仓库源码结构看,dagger-compiler 模块实现了完整的绑定图解析与代码生成逻辑,仓库javatests/internal/codegen下大量的*.DaggerTestComponent_DEFAULT_MODE、*.DaggerMyComponent等测试夹具(golden files)正是对这种生成代码输出形态的验证。
生成类通过builder()暴露一个构建器,build()之后:
- 所有
@Inject构造器(CoffeeLogger、ElectricHeater、Thermosiphon、CoffeeMaker)被框架实例化; @Binds绑定(Heater→ElectricHeater,Pump→Thermosiphon)被解析进绑定图;- 单例在首次使用时创建并缓存;
CoffeeShop作为单例组件持有这些依赖。
运行时行为链
main()的执行路径对应如下调用链:
coffeeShop.maker().brew() → heater.get().on() // Lazy 首次 get(),ElectricHeater.on() → 日志 "~ ~ ~ heating ~ ~ ~" → pump.pump() // Thermosiphon.pump() → 日志 "=> => pumping => =>"(heater 已热) → logger.log(" [_]P coffee! [_]P ") → heater.get().off() // Lazy 第二次 get(),复用同一 heater 实例 coffeeShop.logger().logs() // 取回全部日志并逐行打印注意到Thermosiphon.pump()会检查heater.isHot(),而CoffeeMaker.brew()在泵水之前先heater.get().on()——Lazy与共享实例的配合保证了pump()时加热器确实处于热态。
运行方式:IDE 与命令行
在 Android Studio / IntelliJ IDEA 中打开
- 启动 Android Studio 或 IntelliJ IDEA;
- 选择File>Open...(或在欢迎界面点击Open);
- 导航并选择本示例目录(
examples/gradle/coffee); - IDE 会自动识别 Gradle 工程、导入依赖(
dagger、dagger-compiler)并配置 Java 工具链(17); - 在 IDE 的运行配置或项目文件树中定位到
CoffeeApp.java,点击main()旁边的 Run 按钮即可运行。
运行前 IDE 会自动执行编译,dagger-compiler会在该过程中生成DaggerCoffeeApp_CoffeeShop实现类。
使用命令行运行
在示例目录下使用 Gradle Wrapper(推荐,版本由 wrapper 锁定为 Gradle 9.3.0)运行:
./gradlew run或使用本机安装的系统 Gradle:
gradle run两种方式都依赖application插件与build.gradle中的mainClass = 'example.dagger.CoffeeApp'配置,最终执行example.dagger.CoffeeApp.main()。
预期输出
~ ~ ~ heating ~ ~ ~ => => pumping => => [_]P coffee! [_]P三行输出分别来自ElectricHeater.on()、Thermosiphon.pump()与CoffeeMaker.brew()写入CoffeeLogger的日志,最终由main()统一打印——顺序恰好还原了"加热 → 泵水 → 出杯"的冲煮流程。
小结与进一步探索
这个 Coffee Maker 示例用不到 10 个 Java 文件,完整呈现了 Dagger 的核心工作流:业务类用@Inject声明依赖 →@Module用@Binds绑定接口 →@Component声明模块与入口点 →dagger-compiler编译期生成实现 → 运行时由组件装配对象。其中Lazy展示了延迟创建、@Singleton展示了单例复用,两个概念在实际项目中(尤其是 Android/后端服务中昂贵资源的注入)有极高的使用频率。
如果你希望继续深入,可以:
- 阅读同一示例在 Maven 构建版 与 Bazel 构建版 中的实现,对比三种构建系统接入 Dagger 的差异;
- 在仓库的 javatests/functional 测试目录中查看
basic、builder、subcomponent等更丰富的函数级测试用例,了解@Binds、作用域、子组件等进阶行为的验证方式; - 阅读 dagger-runtime 模块下 Lazy.java、Component.java 等注解与接口的源码注释,掌握更完整的语义约定。
- 后端
- 代码生成
- 依赖注入
【免费下载链接】dagger
A fast dependency injector for Android and Java.
相关推荐
peepdf深度剖析:检测CVE漏洞与恶意代码的技术实现
peepdf深度剖析:检测CVE漏洞与恶意代码的技术实现 peepdf是一款强大的Python工具,专为PDF文档分析设计,能够深度检测PDF文件中的CVE漏洞
终极指南:如何用Univer构建企业级文档协作平台
终极指南:如何用Univer构建企业级文档协作平台 在当今数字化办公时代,企业级文档协作工具的需求日益增长,而Univer作为一套完整的全栈框架,正在重新定义表
前端企业应用设计一个咖啡自动售货机(Coffee Vending Machine):从需求拆解到多语言 LLD 实战
设计一个咖啡自动售货机(Coffee Vending Machine):从需求拆解到多语言 LLD 实战 导读 本文以 problems/coffee vend
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考