1. 简介

本文将带你快速了解 Dagger 2 —— 一个高性能、轻量级的依赖注入(DI)框架。

Dagger 2 支持 Java 和 Android 平台,但由于其依赖编译时代码生成实现注入,因此在 Android 开发中尤为受欢迎。相比运行时反射注入,它的性能更优,启动更快,是目前 Android 领域主流的 DI 框架之一。

2. 依赖注入回顾

简单回顾一下:依赖注入(DI) 是“控制反转(IoC)”思想的一种具体实现方式,即对象不主动创建依赖,而是由外部容器提供所需依赖。

不同框架实现 DI 的方式不同,主要分为两类:

  • 运行时注入:基于反射机制,如 Spring 框架。使用简单,但运行时性能开销较大。
  • 编译时注入:通过注解处理器在编译阶段生成注入代码,如 Dagger 2。启动快、性能高,但学习成本略高。

⚠️ Dagger 2 属于后者 —— 它在编译期生成大量模板代码,避免了运行时反射,因此效率极高。这也是它在 Android 场景中广受青睐的原因。

3. Maven/Gradle 配置

要在项目中使用 Dagger 2,首先需要引入核心依赖和注解处理器。

Maven 配置

<dependency>
    <groupId>com.google.dagger</groupId>
    <artifactId>dagger</artifactId>
    <version>2.16</version>
</dependency>

同时,必须添加 Dagger 编译器作为注解处理器:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.11.0</version>
    <configuration>
         <annotationProcessorPaths>
              <path>
                  <groupId>com.google.dagger</groupId>
                  <artifactId>dagger-compiler</artifactId>
                  <version>2.16</version>
              </path>
         </annotationProcessorPaths>
    </configuration>
</plugin>

⚠️ 生成的代码会放在 target/generated-sources/annotations 目录下。部分 IDE(如 IntelliJ IDEA)需要手动将该目录标记为“Generated Sources Root”,否则无法识别生成的 DaggerXXXComponent 类。

Gradle 配置(Android 推荐)

implementation 'com.google.dagger:dagger:2.16'
annotationProcessor 'com.google.dagger:dagger-compiler:2.16'

✅ 使用 Android Studio 时,Gradle 会自动处理生成目录,无需额外配置。

配置完成后,就可以开始编写 Dagger 代码了。

4. 实现示例:构建一辆车

我们通过一个简单的“造车”例子来演示 Dagger 2 的使用流程。目标是通过依赖注入组装一辆车,其组件包括发动机(Engine)和品牌(Brand)。

4.1 使用 @Inject 标记依赖

Dagger 使用 JSR-330 标准注解,其中 @Inject 是最核心的注解之一。

⚠️ 注意:Dagger 不支持对私有字段进行注入,因此推荐使用构造函数注入来保证封装性。

public class Car {

    private Engine engine;
    private Brand brand;

    @Inject
    public Car(Engine engine, Brand brand) {
        this.engine = engine;
        this.brand = brand;
    }

    // getters and setters
}

这样 Dagger 就知道如何创建 Car 实例,并自动传入所需的 EngineBrand

4.2 创建 Module:提供依赖

Module 是依赖的“供应方”,负责创建对象实例。

  • ✅ 使用 @Module 注解标记类
  • ✅ 使用 @Provides 注解标记提供依赖的方法
@Module
public class VehiclesModule {
    @Provides
    public Engine provideEngine() {
        return new Engine();
    }

    @Provides
    @Singleton
    public Brand provideBrand() { 
        return new Brand("Baeldung"); 
    }
}

✅ 关键点:

  • @Provides 方法名可以自定义,Dagger 通过返回类型匹配依赖。
  • @Singleton 表示该 Brand 实例是单例的,所有 Car 实例共享同一个 Brand 对象。
  • Engine 没有加作用域,默认每次请求都会创建新实例(类似 Spring 的 prototype 模式)。

4.3 创建 Component:注入器接口

Component 是连接 Module 和客户端的桥梁,相当于“装配线”。

  • ✅ 使用 @Component 注解标记接口
  • ✅ 通过 modules 参数指定依赖的 Module
  • ✅ 定义方法用于获取注入后的对象
@Singleton
@Component(modules = VehiclesModule.class)
public interface VehiclesComponent {
    Car buildCar();
}

⚠️ 注意:

  • 必须将 VehiclesModule.class 传入 @Component(modules = ...),否则 Dagger 无法找到依赖提供方。
  • 因为 Module 中定义了 @Singleton 绑定,所以 Component 也必须标注 @Singleton,否则编译报错。Dagger 要求作用域匹配,不能让无作用域的 Component 引用有作用域的绑定

4.4 客户端使用

编译项目后,Dagger 会自动生成 DaggerVehiclesComponent 类(前缀 Dagger + 接口名),这是 Component 的实现类。

@Test
public void givenGeneratedComponent_whenBuildingCar_thenDependenciesInjected() {
    VehiclesComponent component = DaggerVehiclesComponent.create();

    Car carOne = component.buildCar();
    Car carTwo = component.buildCar();

    Assert.assertNotNull(carOne);
    Assert.assertNotNull(carTwo);
    Assert.assertNotNull(carOne.getEngine());
    Assert.assertNotNull(carTwo.getEngine());
    Assert.assertNotNull(carOne.getBrand());
    Assert.assertNotNull(carTwo.getBrand());
    Assert.assertNotEquals(carOne.getEngine(), carTwo.getEngine()); // Engine 非单例
    Assert.assertEquals(carOne.getBrand(), carTwo.getBrand());     // Brand 单例
}

✅ 输出验证:

  • 两辆车的 Engine 实例不同(每次新建)
  • 两辆车的 Brand 实例相同(单例共享)

这就是作用域控制的实际效果。

5. 与 Spring 的对比

如果你熟悉 Spring,会发现 Dagger 和 Spring 在概念上有不少对应关系:

Spring Dagger 2 说明
@Service / @Component @Module 声明一个组件类
@Bean @Provides 提供一个 Bean 实例
@Lookup Component 方法 获取容器中的对象
@Scope / @Singleton @Singleton 控制对象生命周期

⚠️ 重要区别:

  • 默认作用域不同:Spring 默认是 singleton,而 Dagger 默认是“无作用域”,即每次请求都创建新实例(类似 prototype)。
  • 注入时机不同:Spring 是运行时反射,Dagger 是编译时生成代码,性能更高。

简单粗暴地说:Dagger 把 Spring 容器在运行时做的事,提前到编译期完成了

6. 总结

本文通过一个简单的汽车组装示例,介绍了 Dagger 2 的基本使用流程:

  1. 使用 @Inject 标记构造函数
  2. 使用 @Module + @Provides 提供依赖
  3. 使用 @Component 定义注入接口
  4. 编译后使用生成的 DaggerXXXComponent 获取实例

Dagger 2 的优势在于高性能和类型安全,适合对启动速度和内存敏感的场景(如 Android App)。虽然初期学习曲线稍陡,但一旦掌握,能有效解耦代码,提升可测试性。

✅ 所有示例代码已托管至 GitHub:

https://github.com/baeldung/tutorials/tree/master/di-modules/dagger


原始标题:Introduction to Dagger 2 | Baeldung