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

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM