使用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基本差不多, 各个注解概念也类似,
最大的区别就是非动态注入, 非反射实现, 而是编译期静态注入.
- MongoDB-基础-条件操作符
- mongodb-基础-update-remove
- Mybatis-mapper-xml-基础
- 2016.06 第一周 群问题分享
- MongoDB - basic
- spring boot启用tomcat ssl
- HTTP status code
- jackson简单使用,对象转json,json转对象,json转list
- 2016.07 第3周 群问题分享
- jackson error 含义log
- java删除文件夹
- JSP自定义tag
- gradle中使用嵌入式(embedded) tomcat, debug 启动
- spring in action 4th --- quick start
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 聊聊claudb的string command
- 为什么你使用的 Spring Security OAuth 过期了?松哥来和大家捋一捋!
- 一个诡异的登录问题
- 什么是计时攻击?Spring Boot 中该如何防御?
- XSS 攻击详解,为什么建议 Cookie 加上 HttpOnly 属性?
- 个性化调整坐标轴的颜色和位置
- matplotlib实现一页多图
- 用matplotlib实现画中画
- 为matplotlib设置不同的主题
- 模式利器 | MEIC污染源清单向WRF-Chem模式网格插值分配工具——meic2wrf
- Windows给力!可以扔掉Linux虚拟机了!
- 好慌,Redis这么多集群方案,要用哪种?
- 你真的了解MySQL 8.0 数据字典吗?
- 《调教命令行09》磁盘很廉价,放小电影最合适了
- 既保护隐私又快速训练,Facebook开源Opacus库,利用差分隐私训练PyTorch模型