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 方法依赖是必需的。