使用Dagger2做静态注入, 对比Guice.

时间:2022-04-24
本文章向大家介绍使用Dagger2做静态注入, 对比Guice.,主要内容包括Dagger、构建工程、第一个注入程序、注入接口对象、接口存在多个实现、Scope生命周期、Lazy 延迟初始化、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

Dagger

依赖注入的诉求, 这边就不重复描述了, 在上文Spring以及Guice的IOC文档中都有提及, 既然有了Guice,

Google为啥还要搞个Dagger2出来重复造轮子呢? 因为使用动态注入, 虽然写法简单了, 耦合也降低了,

但是带来了调试不方便, 反射性能差等一些缺点.

而Dagger跟Guice最大的差异在于, 他是编译期注入的, 而不是运行时.

他生成的代码可以直观的调试, 也不是通过反射, 而是通过构建工厂类. 下面我们用代码来简单演示一下.

构建工程

既然Dagger是静态注入的, 那么他自然也跟其他动态注入框架工程有点区别,

编译时需要额外依赖dagger-compiler, dagger-producers等,

不过运行时的jar只需要dagger以及javax.inject包即可.

好在Google为我们提供了pom文件, 我们只需要在idea里新建maven工程, 在pom文件中导入如下内容, 他会自动下载依赖.

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
 3          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5     <modelVersion>4.0.0</modelVersion>
 6 
 7     <groupId>com.maven.dagger2</groupId>
 8     <artifactId>com.maven.dagger2</artifactId>
 9     <version>1.0-SNAPSHOT</version>
10 
11     <dependencies>
12         <dependency>
13             <groupId>com.google.dagger</groupId>
14             <artifactId>dagger</artifactId>
15             <version>2.2</version>
16         </dependency>
17         <dependency>
18             <groupId>com.google.dagger</groupId>
19             <artifactId>dagger-compiler</artifactId>
20             <version>2.2</version>
21             <optional>true</optional>
22         </dependency>
23     </dependencies>
24 </project>

第一个注入程序

我们以一个打印系统为例, 打印业务类PrintJob, 里面有一份报表Reportpage待打印.

1 public class ReportPage{
2 
3     public void print(){
4         System.out.println("开始打印报表");
5     }
6 }
 1 public class PrintJob {
 2     // 需要打印的报表 
 3     public ReportPage reportPage;
 4 
 5     public void setReportPage(ReportPage reportPage) {
 6         this.reportPage = reportPage;
 7     }
 8 
 9     public void print() {
10         this.reportPage.print();
11     }
12 
13     public static void main(String[] args) throws InterruptedException {
14         // 初始化报表
15         ReportPage page = new ReportPage();
16         PrintJob job = new PrintJob();
17         job.setReportPage(page);
18         //执行打印
19         job.print();
20     }
21 }

在main函数中, 我们初始化了Printjob以及它里面的报表对象, 并执行打印.

下面我们通过Dagger注入的方式来写.

写法很简单, 跟Guice类似, 我们只需要在reportpage成员上加@Inject注解.

同时添加一个Component对象, 用来告诉Dagger, 应该注入到该类, 并扫描其中@Inject的成员

1 @Component
2 public interface PrintjobComponent {
3 
4     void inject(PrintJob job);
5 }

添加完Component以及@Inject注解后我们需要编译代码或者rebuild工程, 让Dagger为我们生成工厂类.

生成的代码位于target/generated-sources目录. 里面会有一个叫DaggerPrintjobComponent的类.

idea会自动将当期路径标记成Classpath, 因此我们也不需要把他手动拷贝出来.

如果没有自动import, 可以右键pom.xml->Maven ->Reimport.

我们在Printjob的构造函数里加上DaggerPrintjobComponent.create().inject(this);来实现注入

 1 public class PrintJob {
 2 
 3     @Inject
 4     public ReportPage reportPage;
 5 
 6     public PrintJob() {
 7         DaggerPrintjobComponent.create().inject(this);
 8     }
 9 
10     public void print() {
11         this.reportPage.print();
12     }
13 
14     public static void main(String[] args) throws InterruptedException {
15         // 看上去清爽了一点
16         PrintJob job = new PrintJob();
17         job.print();
18     }
19 }
 1 public class ReportPage {
 2 
 3     @Inject
 4     public ReportPage() {
 5         System.out.println("初始化成功!!!");
 6     }
 7 
 8     public void print(){
 9         System.out.println("开始打印报表");
10     }
11 }

相比于一开始的非注入写法, 在外部是看不到赋值操作的.

有人会说, 那我直接在printjob的构造函数里new reportpage()不就行了, 为什么要这么费事呢.

原因很简单, 大型系统里, printjob只存在一个接口, 他无法, 也不需要直接new reportpage()对象.

下面演示如何注入接口对象.

注入接口对象

我们给reportpage增加一个接口, 并在printjob中修改为接口声明.

1 public class ReportPage implements ReportPageProvider{
1 public interface ReportPageProvider {
2 
3     void print();
4 }
1 public class PrintJob {
2 
3     @Inject
4     public ReportPageProvider reportPage;

这个时候会发现, 运行注入报错了, 原因很简单, 我们@inject依然加载reportpage对象上,

此时他是一个接口, 接口是无法直接被实例化的.

因此我们需要引入Module对象来处理接口, 其实就是类似于一个工厂提供类.

1 @Module
2 public class ReportPageModule {
3 
4     @Provides
5     public ReportPageProvider createPage() {
6         return new ReportPage();
7     }
8 }

然后在component中引入module, 其他代码不用改, 依然直接new printjob().print()对象.

1 @Component(modules = ReportPageModule.class)
2 public interface PrintjobComponent {
3 
4     void inject(PrintJob job);
5 }

接口存在多个实现

我们给ReportpageProvider再增加一个子类NewReportPage, 修改Module, 增加一个方法, 构造NewReportPage.

 1 @Module
 2 public class ReportPageModule {
 3 
 4     @Provides
 5     public ReportPageProvider createPage() {
 6         return new ReportPage();
 7     }
 8 
 9     @Provides
10     public ReportPageProvider createNewReportPage() {
11         return new NewReportPage();
12     }
13 
14 }

这个时候直接编译是无法通过的, 相同返回类型的provider只能添加一个, 如果添加多个, dagger将报错, 存在多个提供类.

此时我们就要跟Guice里一样, 使用@Named注解来标识了

1     @Named("new")
2     public ReportPageProvider reportPage;

调用的时候也很简单

1     @Inject
2     @Named("new")
3     public ReportPageProvider reportPage;

同理, 也可以通过@Qualifier来自定义注解标识.

1 @Qualifier
2 @Retention(RetentionPolicy.RUNTIME)
3 public @interface NewReportMark {}

然后在调用的地方加上 @NewReportMark即可.

Scope生命周期

默认对象都是每次都new的, 如果想要单例实现, 则需要添加@Singleton.

在Component以及Module都加上Singleton注解.

1 @Singleton
2 @Component(modules = ReportPageModule.class)
3 public interface PrintjobComponent {
4 
5     void inject(PrintJob job);
6 }
1     @Provides
2     @Named("new")
3     @Singleton
4     public ReportPageProvider createNewReportPage() {
5         return new NewReportPage();
6     }

我们给Printjob中再增加一个reportpage对象, 并打印他们的hashcode.

 1     @Inject
 2     @Named("new")
 3     public ReportPageProvider reportPage;
 4 
 5     @Inject
 6     @Named("new")
 7     public ReportPageProvider reportPage2;
 8 
 9 ......
10 
11     PrintJob job = new PrintJob();
12     System.out.println(job.reportPage);
13     System.out.println(job.reportPage2);

加上Singleton注解后, 打印出来的hashcode是一致的了.

但是, 如果我们再new 一个Printjob, 打印他的reportpage.

1         PrintJob job = new PrintJob();
2         System.out.println(job.reportPage);
3         System.out.println(job.reportPage2);
4 
5         PrintJob job2 = new PrintJob();
6         System.out.println(job2.reportPage);
7         System.out.println(job2.reportPage2);

会发现前两个的hashcode跟后两个的不一样, 这就很蛋疼了. 他只是一个作用于当前component的伪单例.

那么如何实现真单例呢, 其实就是想办法把Component搞成单例的.

这样他里面的对象也都是同一个作用域下的单例了.

我们添加一个SingletonPrintjobComponent, 写法与PrintjobComponent一致.

编译后生成DaggerSingletonPrintjobComponent. 然后修改printjob构造函数中的注入.

DaggerPrintjobComponent.create().inject(this); 改成如下:

 1 public class PrintJob {
 2 
 3     private static SingletonPrintjobComponent component = DaggerSingletonPrintjobComponent.create();
 4 
 5     @Inject
 6     @Named("new")
 7     public ReportPageProvider reportPage;
 8 
 9     @Inject
10     @Named("new")
11     public ReportPageProvider reportPage2;
12 
13     public PrintJob() {
14         component.inject(this);
15     }
16 
17     public void print() {
18         this.reportPage.print();
19     }
20 
21     public static void main(String[] args) throws InterruptedException {
22         PrintJob job = new PrintJob();
23         System.out.println(job.reportPage);
24         System.out.println(job.reportPage2);
25 
26         PrintJob job2 = new PrintJob();
27         System.out.println(job2.reportPage);
28         System.out.println(job2.reportPage2);
29     }
30 }

这样的话, 多个printjob打印出来的reportpage就是一致的了, 因为都是位于同一个static的component中.

Lazy 延迟初始化

默认对象是inject的时候初始化, 如果使用Lazy封装一下, 则可以在get的时候再初始化.

1     @Inject
2     @Named("old")
3     public Lazy<ReportPageProvider> oldReportPage;
1         PrintJob job = new PrintJob();
2         Thread.sleep(3000);
3         // 对象会在get()方法调用的时候触发初始化
4         job.oldReportPage.get().print();

到这边就结束了, 可以看到Dagger使用上跟Guice基本差不多, 各个注解概念也类似,

最大的区别就是非动态注入, 非反射实现, 而是编译期静态注入.