使用Dagger2做靜態註入, 對比Guice.
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.