依赖注入的诉求, 这边就不重复描述了, 在上文 Spring 以及 Guice 的 IOC 文档 中都有提及, 既然有了 Guice,
Google 为啥还要搞个 Dagger2 出来重复造轮子呢? 因为使用动态注入, 虽然写法简单了, 耦合也降低了,
但是带来了调试不方便, 反射性能差等一些缺点.
而 Dagger 跟 Guice 最大的差异在于, 他是编译期注入的, 而不是运行时.
他生成的代码可以直观的调试, 也不是通过反射, 而是通过构建工厂类. 下面我们用代码来简单演示一下.
既然 Dagger 是静态注入的, 那么他自然也跟其他动态注入框架工程有点区别,
编译时需要额外依赖 dagger-compiler, dagger-producers 等,
不过运行时的 jar 只需要 dagger 以及 javax.inject 包即可.
好在 Google 为我们提供了 pom 文件, 我们只需要在 idea 里新建 maven 工程, 在 pom 文件中导入如下内容, 他会自动下载依赖.
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.maven.dagger2</groupId>
- <artifactId>com.maven.dagger2</artifactId>
- <version>1.0-SNAPSHOT</version>
- <dependencies>
- <dependency>
- <groupId>com.google.dagger</groupId>
- <artifactId>dagger</artifactId>
- <version>2.2</version>
- </dependency>
- <dependency>
- <groupId>com.google.dagger</groupId>
- <artifactId>dagger-compiler</artifactId>
- <version>2.2</version>
- <optional>true</optional>
- </dependency>
- </dependencies>
- </project>
我们以一个打印系统为例, 打印业务类 PrintJob, 里面有一份报表 Reportpage 待打印.
- public class ReportPage {
- public void print() {
- System.out.println("开始打印报表");
- }
- }
- public class PrintJob { // 需要打印的报表
- public ReportPage reportPage;
- public void setReportPage(ReportPage reportPage) {
- this.reportPage = reportPage;
- }
- public void print() {
- this.reportPage.print();
- }
- public static void main(String[] args) throws InterruptedException {
- // 初始化报表
- ReportPage page = new ReportPage();
- PrintJob job = new PrintJob();
- job.setReportPage(page);
- //执行打印
- job.print();
- }
- }
在 main 函数中, 我们初始化了 Printjob 以及它里面的报表对象, 并执行打印.
下面我们通过 Dagger 注入的方式来写.
写法很简单, 跟 Guice 类似, 我们只需要在 reportpage 成员上加 @Inject 注解.
同时添加一个 Component 对象, 用来告诉 Dagger, 应该注入到该类, 并扫描其中 @Inject 的成员
- @Component
- public interface PrintjobComponent {
- void inject(PrintJob job);
- }
添加完 Component 以及 @Inject 注解后我们需要编译代码或者 rebuild 工程, 让 Dagger 为我们生成工厂类.
生成的代码位于 target/generated-sources 目录. 里面会有一个叫 DaggerPrintjobComponent 的类.
idea 会自动将当期路径标记成 Classpath, 因此我们也不需要把他手动拷贝出来.
如果没有自动 import, 可以右键 pom.xml->Maven ->Reimport.
我们在 Printjob 的构造函数里加上 DaggerPrintjobComponent.create().inject(this); 来实现注入
- public class PrintJob {
- @Inject public ReportPage reportPage;
- public PrintJob() {
- DaggerPrintjobComponent.create().inject(this);
- }
- public void print() {
- this.reportPage.print();
- }
- public static void main(String[] args) throws InterruptedException {
- // 看上去清爽了一点
- PrintJob job = new PrintJob();
- job.print();
- }
- }
- public class ReportPage {
- @Inject
- public ReportPage() {
- System.out.println("初始化成功!!!");
- }
- public void print(){
- System.out.println("开始打印报表");
- }
- }
相比于一开始的非注入写法, 在外部是看不到赋值操作的.
有人会说, 那我直接在 printjob 的构造函数里 new reportpage() 不就行了, 为什么要这么费事呢.
原因很简单, 大型系统里, printjob 只存在一个接口, 他无法, 也不需要直接 new reportpage() 对象.
下面演示如何注入接口对象.
我们给 reportpage 增加一个接口, 并在 printjob 中修改为接口声明.
- 1 public class ReportPage implements ReportPageProvider {
- public interface ReportPageProvider {
- void print();
- }
- public class PrintJob {
- @Inject
- public ReportPageProvider reportPage;
这个时候会发现, 运行注入报错了, 原因很简单, 我们 @inject 依然加载 reportpage 对象上,
此时他是一个接口, 接口是无法直接被实例化的.
因此我们需要引入 Module 对象来处理接口, 其实就是类似于一个工厂提供类.
- @Module
- public class ReportPageModule {
- @Provides
- public ReportPageProvider createPage() {
- return new ReportPage();
- }
- }
然后在 component 中引入 module, 其他代码不用改, 依然直接 new printjob().print() 对象.
- @Component(modules = ReportPageModule.class)
- public interface PrintjobComponent {
- void inject(PrintJob job);
- }
我们给 ReportpageProvider 再增加一个子类 NewReportPage, 修改 Module, 增加一个方法, 构造 NewReportPage.
- @Module
- public class ReportPageModule {
- @Provides
- public ReportPageProvider createPage() {
- return new ReportPage();
- }
- @Provides
- public ReportPageProvider createNewReportPage() {
- return new NewReportPage();
- }
- }
这个时候直接编译是无法通过的, 返回类型的 provider 只能添加一个, 如果添加多个, dagger 将报错, 存在多个提供类.
此时我们就要跟 Guice 里一样, 使用 @Named 注解来标识了
- @Named("new")
- public ReportPageProvider reportPage;
调用的时候也很简单
- @Inject
- @Named("new")
- public ReportPageProvider reportPage;
同理, 也可以通过 @Qualifier 来自定义注解标识.
- @Qualifier
- @Retention(RetentionPolicy.RUNTIME)
- public @interface NewReportMark {}
然后在调用的地方加上 @NewReportMark 即可.
默认对象都是每次都 new 的, 如果想要单例实现, 则需要添加 @Singleton.
在 Component 以及 Module 都加上 Singleton 注解.
- @Singleton
- @Component(modules = ReportPageModule.class)
- public interface PrintjobComponent {
- void inject(PrintJob job);
- }
- @Provides
- @Named("new")
- @Singleton
- public ReportPageProvider createNewReportPage() {
- return new NewReportPage();
- }
我们给 Printjob 中再增加一个 reportpage 对象, 并打印他们的 hashcode.
- @Inject
- @Named("new")
- public ReportPageProvider reportPage;
- @Inject
- @Named("new")
- public ReportPageProvider reportPage2;
- ......
- PrintJob job = new PrintJob();
- System.out.println(job.reportPage);
- System.out.println(job.reportPage2);
加上 Singleton 注解后, 打印出来的 hashcode 是一致的了.
但是, 如果我们再 new 一个 Printjob, 打印他的 reportpage.
- PrintJob job = new PrintJob();
- System.out.println(job.reportPage);
- System.out.println(job.reportPage2);
- PrintJob job2 = new PrintJob();
- System.out.println(job2.reportPage);
- System.out.println(job2.reportPage2);
会发现前两个的 hashcode 跟后两个的不一样, 这就很蛋疼了. 他只是一个作用于当前 component 的伪单例.
那么如何实现真单例呢, 其实就是想办法把 Component 搞成单例的.
这样他里面的对象也都是同一个作用域下的单例了.
我们添加一个 SingletonPrintjobComponent, 写法与 PrintjobComponent 一致.
编译后生成 DaggerSingletonPrintjobComponent. 然后修改 printjob 构造函数中的注入.
DaggerPrintjobComponent.create().inject(this); 改成如下:
- public class PrintJob {
- private static SingletonPrintjobComponent component = DaggerSingletonPrintjobComponent.create();
- @Inject
- @Named("new")
- public ReportPageProvider reportPage;
- @Inject
- @Named("new")
- public ReportPageProvider reportPage2;
- public PrintJob() {
- component.inject(this);
- }
- public void print() {
- this.reportPage.print();
- }
- public static void main(String[] args) throws InterruptedException {
- PrintJob job = new PrintJob();
- System.out.println(job.reportPage);
- System.out.println(job.reportPage2);
- PrintJob job2 = new PrintJob();
- System.out.println(job2.reportPage);
- System.out.println(job2.reportPage2);
- }
- }
这样的话, 多个 printjob 打印出来的 reportpage 就是一致的了, 因为都是位于同一个 static 的 component 中.
Lazy 延迟初始化
默认对象是 inject 的时候初始化, 如果使用 Lazy 封装一下, 则可以在 get 的时候再初始化.
- @Inject
- @Named("old")
- public Lazy<ReportPageProvider> oldReportPage;
- PrintJob job = new PrintJob();
- Thread.sleep(3000);
- // 对象会在get()方法调用的时候触发初始化
- job.oldReportPage.get().print();
到这边就结束了, 可以看到 Dagger 使用上跟 Guice 基本差不多, 各个注解概念也类似,
最大的区别就是非动态注入, 非反射实现, 而是编译期静态注入.
来源: http://www.cnblogs.com/xdecode/p/8086906.html