1. 程式人生 > >使用Dagger2做靜態註入, 對比Guice.

使用Dagger2做靜態註入, 對比Guice.

cati named pom 類型 發現 ref ava ldr ger

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基本差不多, 各個註解概念也類似,

最大的區別就是非動態註入, 非反射實現, 而是編譯期靜態註入.

使用Dagger2做靜態註入, 對比Guice.