Contents

Dagger 2 简介

1. 简介

在本教程中,我们将了解 Dagger 2——一个快速且轻量级的依赖注入框架。

该框架可用于 Java 和 Android,但源自编译时注入的高性能使其成为后者的领先解决方案。

2. 依赖注入

提醒一下,依赖注入 是更通用的控制反转原则的具体应用,其中程序的流程由程序本身控制。

它是通过一个外部组件实现的,该组件提供其他对象所需的对象(或依赖项)实例。

并且不同的框架以不同的方式实现依赖注入。特别是,这些差异中最显着的一个是注入是发生在运行时还是编译时。

运行时 DI 通常基于反射,这种反射更易于使用,但运行时速度较慢。运行时 DI 框架的 一个示例是 Spring 。

另一方面,编译时 DI 是基于代码生成的。这意味着所有重量级操作都在编译期间执行。编译时 DI 增加了复杂性,但通常执行得更快。

Dagger 2 属于这一类。

3. Maven/Gradle 配置

为了在项目中使用 Dagger,我们需要将 dagger依赖添加  到我们的 pom.xml中:

<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.6.1</version>
    <configuration>
         <annotationProcessorPaths>
              <path>
                  <groupId>com.google.dagger</groupId>
                  <artifactId>dagger-compiler</artifactId>
                  <version>2.16</version>
              </path>
         </annotationProcessorPaths>
    </configuration>
</plugin>

使用此配置,Maven 会将生成的代码输出到 target/generated-sources/annotations中。

出于这个原因, 如果我们想使用它的任何代码完成功能,**我们可能需要进一步配置我们的 IDE 。**一些 IDE 直接支持注释处理器,而其他 IDE 可能需要我们将此目录添加到构建路径中。

或者,如果我们使用带有 Gradle 的 Android,我们可以包含两个依赖项:

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

现在我们的项目中有 Dagger,让我们创建一个示例应用程序来看看它是如何工作的。

4. 实施

对于我们的示例,我们将尝试通过注入其组件来构建汽车。

现在,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
}

接下来,我们将实现执行注入的代码。更具体地说,我们将创建:

  • 一个Module,它是一个提供或构建对象依赖项的类,以及
  • 一个Component,它是一个用于生成注入器的接口

复杂的项目可能包含多个模块和组件,但由于我们处理的是一个非常基本的程序,因此每个程序一个就足够了。 让我们看看如何实现它们。

4.1. Module

要创建一个模块,我们需要使用@Module*注释对类进行注释*。此注解表明该类可以使容器可以使用依赖项:

@Module
public class VehiclesModule {
}

然后, 我们需要在 构造依赖项的方法上添加@Provides*注解:*

@Module
public class VehiclesModule {
    @Provides
    public Engine provideEngine() {
        return new Engine();
    }
    @Provides
    @Singleton
    public Brand provideBrand() { 
        return new Brand("Blogdemo"); 
    }
}

另外,请注意,我们可以配置给定依赖项的范围。在这种情况下,我们将单例范围赋予我们的 Brand实例,以便所有汽车实例共享相同的品牌对象。

4.2. Component

继续,我们将创建我们的组件接口。这个类将生成 Car 实例,注入VehiclesModule提供的依赖项。

简单地说,我们需要一个返回Car的方法签名,并且我们需要使用*@Component*注释标记该类:

@Singleton
@Component(modules = VehiclesModule.class)
public interface VehiclesComponent {
    Car buildCar();
}

请注意我们如何将模块类作为参数传递给*@Component*注释。 如果我们不这样做,Dagger 将不知道如何构建汽车的依赖项。

此外,由于我们的模块提供了一个单例对象,我们必须为我们的组件提供相同的范围,因为Dagger 不允许无范围的组件引用范围绑定

4.3. 客户代码

最后,我们可以运行 mvn compile以触发注释处理器并生成注入器代码。

之后,我们会发现我们的组件实现与接口同名,只是前缀为“ Dagger ”:

@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());
    Assert.assertEquals(carOne.getBrand(), carTwo.getBrand());
}

5. 对比 Spring

熟悉 Spring 的人可能已经注意到这两个框架之间的一些相似之处。

Dagger 的*@Module注解使容器以与 Spring 的任何原型注解(例如 @Service@Controller* …)非常相似的方式识别类。同样,@Provides和*@Component* 几乎分别相当于Spring 的*@Bean@Lookup*。

Spring 也有它的*@Scope注释,与@Singleton*相关,尽管这里已经注意到另一个区别在于 Spring 默认假设一个单例范围,而 Dagger 默认为 Spring 开发人员可能称为原型范围,每次调用 provider 方法依赖是必需的。