zoukankan      html  css  js  c++  java
  • 使用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基本差不多, 各个注解概念也类似,

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

  • 相关阅读:
    JS正则手机靓号处理AB ABAB AABB
    url参数中有+、空格、=、%、&、#等特殊符号的问题解决
    LigerUI 使用教程表格篇
    JS获取地址栏参数
    手机端meta
    最大连续子序列 hdu 1231
    I NEED A OFFER! hdu1203(背包)
    Bookshelf 2 poj3628(01背包/DFS)
    Charm Bracelet poj3624(01背包)
    985的方格难题(dp)
  • 原文地址:https://www.cnblogs.com/xdecode/p/8086906.html
Copyright © 2011-2022 走看看