模块解耦神器-Dagger2的使用和详解

1 Dagger2简介

Dagger2提供给Java和Android使用,主要用于模块间解耦、提高代码的健壮性和可维护性。它是一款依赖注入框架,使用了IOC(控制反转)的思想,在编译阶段使用APT利用Java注解生成Java代码,然后结合部分手写代码来完整依赖注入工作。

运行前需要先编译一次项目,目的是用APT生成中间代码。Dagger2不使用反射,在编译阶段生成代码,所以不会程序性能有影响。

IOC(控制反转)和依赖注入的区别:

控制反转是一种在软件工程中解耦合的思想,调用类只依赖接口,而不依赖具体的实现类,减少了耦合。 控制权交给了容器,在运行的时候才由容器决定将具体的实现动态的“注入”到调用类的对象中。 依赖注入是一种设计模式,可以作为控制反转的一种实现方式。

Dagger2官网:https://dagger.dev/

Dagger2 Github:https://github.com/google/dagger

2 Dagger2原理解析

Dagger2原理.png

可以使用用户在网上买东西,然后通过快递配送来理解Dagger2的使用原理。用户购买一本书(@Inject注解),然后卖家(Module)进行打包,接着快递员(Component)配送,然后用户就收到了这本书。这样就达到了解耦的目的,只需要使用@Inject注解,就能获取到该对象。

3 Dagger2中的注解

Dagger2通过注解使用APT生成代码,所以首先要知道Dagger2中常用注解的使用。

3.1 @Inject

@Inject有两种用处,一是用来标注需要依赖获取的对象,比如:

public class FirstActivity extends AppCompatActivity {
    @Inject
    Book book;

    ...
}

就表示在当前类中,需要依赖Book。

二是用来标注构造方法,Dagger2通过@Inject注解可以在需要这个类实例的时候来找到这个构造函数并把相关实例构造出来,以此来为被@Inject标记了的变量提供依赖,同时会在build下生成对应的xxx_Factory类,后面示例中会详解。

public class Book {

    @Inject
    public Book() {
    }
}

3.2 @Module

@Module用来标注提供依赖对象的类,也就是“包裹”Module类,Module类中会有一个方法来提供依赖对象,在这个方法中可以对注入对象的有参构造函数传入参数或者进行其他处理。

3.3 @Provides

@Provides用来标注Module类中的提供依赖对象的方法进行标注,该方法在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Inject 的变量赋值。

@Module@Provides标注的类在build下会生成对应的xxxModule_xxxFactory类。

3.4 @Component

@Component用来标注接口,该接口提供了方法用来传入业务层,是业务层和Module之间的连接器。标注后会在build下生成DaggerxxxComponent类。

3.5 @Singleton

默认情况下,@Inject获取到的依赖对象是非单例的,要想实现单例,需要用@Singleton对Module中的provide方法和Conponent接口进行标注。

4 Dagger2使用

引入依赖:

implementation 'com.google.dagger:dagger:2.41'
annotationProcessor 'com.google.dagger:dagger-compiler:2.41'

4.1 获取到的依赖注入对象是非单例

创建商品Book。

public class Book {
    @Inject
    public Book() {
    }
}

创建包裹Module。

@Module
public class BookModule {

    @Provides
    public Book provideBook() {
        return new Book();
    }
}

创建快递员Component。

@Component(modules = BookModule.class)
public interface BookComponent {
    void injectFirstActivity(FirstActivity activity);

    void injectSecondActivity(SecondActivity activity);
}

创建两个FirstActivity,通过@Inject获取两个依赖注入对象Book,并打印两个Book的hasCode。

public class FirstActivity extends AppCompatActivity {

    private static final String TAG = "zhangmushui";

    @Inject
    Book book;

    @Inject
    Book book2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);

        //DaggerBookComponent是由dagger-compiler通过APT自动生成的
        DaggerBookComponent.builder()
            .bookModule(new BookModule())
            .build().injectFirstActivity(this);

        //另个Book的hashCode不一样,说明Dagger2提供的Book默认是非单例的
        Log.d(TAG, "book: " + book.hashCode());
        Log.d(TAG, "book2: " + book2.hashCode());

        findViewById(R.id.button).setOnClickListener(v ->
                                                     startActivity(new Intent(this, SecondActivity.class)));
    }
}

创建SecondActivity

public class SecondActivity extends AppCompatActivity {

    private static final String TAG = "zhangmushui";

    @Inject
    Book book;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        DaggerBookComponent.builder()
                .bookModule(new BookModule())
                .build().injectSecondActivity(this);

        Log.d(TAG, "book: " + book.hashCode());
    }
}

控制台可以看到,两个页面中的3个Book对象的hasCode都不同,可见通过Dagger2默认获取到的依赖注入对象是非单例的。

2022-04-20 17:00:04.388 6649-6649/cn.zhangmushui.dagger D/zhangmushui: book2: 94427313
2022-04-20 17:00:16.220 6649-6649/cn.zhangmushui.dagger D/zhangmushui: book: 63721347
2022-04-20 18:46:50.724 6649-6649/cn.zhangmushui.dagger D/zhangmushui: book: 194950979

4.2 获取到的依赖注入对象是局部单例

首先在Module的provide方法上加上@Singleton,需要注意一点的是,之前版本的Dagger2需要在Module类上也添加@Singleton注解,新版本的已经不需要。

/**
 * 想要提供的对象是单例,需要加上@Singleton注解
 * 但是最新版本的Dagger2已经不需要在Module上添加@Singleton注解(不然编译会报错),只需要在下面的provider和Component上添加@Singleton即可。
 */
@Module
public class BookModule {

    @Singleton
    @Provides
    public Book provideBook() {
        return new Book();
    }
}

然后在Component类上添加@Singleton注解。

@Singleton
@Component(modules = BookModule.class)
public interface BookComponent {
    void injectFirstActivity(FirstActivity activity);

    void injectSecondActivity(SecondActivity activity);
}

重新编译后运行,可以看到FirstActivity中的两个Book的hashCode是相同的,SecondActivity和FirstActivity中的Book对象hashCode是不一样的,可见实现了局部单例。

2022-04-20 19:11:04.306 9367-9367/cn.zhangmushui.dagger D/zhangmushui: book: 29400920
2022-04-20 19:11:04.306 9367-9367/cn.zhangmushui.dagger D/zhangmushui: book2: 29400920
2022-04-20 19:11:06.047 9367-9367/cn.zhangmushui.dagger D/zhangmushui: book: 146490930

4.3 获取到的依赖注入对象是全局单例

在上边的基础上继续做修改,创建Application,在Application中实例化Component对象,并提供获取Component的方法。

public class MainApplication extends Application {

    private BookComponent mBookComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        mBookComponent = DaggerBookComponent.builder()
                .bookModule(new BookModule())
                .build();

    }

    public BookComponent getBookComponent() {
        return mBookComponent;
    }
}

然后在Activity中通过Application去获取Component。

public class FirstActivity extends AppCompatActivity {

    private static final String TAG = "zhangmushui";

    @Inject
    Book book;

    @Inject
    Book book2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);

        //在Application中获取Component,实现全局单例
        ((MainApplication) getApplication()).getBookComponent().injectFirstActivity(this);

        //两个Book的。hashCode一致。
        Log.d(TAG, "book: " + book.hashCode());
        Log.d(TAG, "book2: " + book2.hashCode());

        findViewById(R.id.button).setOnClickListener(v ->
                startActivity(new Intent(this, SecondActivity.class)));
    }
}
public class SecondActivity extends AppCompatActivity {

    private static final String TAG = "zhangmushui";

    @Inject
    Book book;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        ((MainApplication) getApplication()).getBookComponent().injectSecondActivity(this);

        //SecondActivity和FirstActivity中的Book的hashCode一致。
        Log.d(TAG, "book: " + book.hashCode());
    }
}

编译后运行查看日志,发现两个页面的3个Book的hashCode都是一样的,这就实现了全局单例。

2022-04-20 19:15:13.890 9544-9544/cn.zhangmushui.dagger D/zhangmushui: book: 29400920
2022-04-20 19:15:13.890 9544-9544/cn.zhangmushui.dagger D/zhangmushui: book2: 29400920
2022-04-20 19:15:23.048 9544-9544/cn.zhangmushui.dagger D/zhangmushui: book: 29400920

5 Dagger2原理解析

接下来以默认非单例的版本去分析Dagger2的原理。

所有用注解标注的类,Dagger2都会用APT在build下去生成对应的辅助类,这些辅助类的名字是根据dagger-compiler注解依赖库中的命名规则生成的。

手动创建 APT生成 命名规则 注解
FirstActivity FirstActivity_MembersInjector 类名_MembersInjector @Inject
SecondActivity SecondActivity_MembersInjector 类名_MembersInjector @Inject
BookComponent DaggerBookComponent Dagger对象名Component
@Component
BookModule BookModule_ProvideBookFactory 对象名Module_方法名Factory @Module @Provides
Book Book_Factory 对象名_Factory @Inject

为了方便观看,原理直接写到注释里边,注释前边用数字标出,表示大概的调用顺序。

// Generated by Dagger (https://dagger.dev).
package cn.zhangmushui.dagger.demo1;

import cn.zhangmushui.dagger.demo1.activity.FirstActivity;
import cn.zhangmushui.dagger.demo1.activity.FirstActivity_MembersInjector;
import cn.zhangmushui.dagger.demo1.activity.SecondActivity;
import cn.zhangmushui.dagger.demo1.activity.SecondActivity_MembersInjector;
import dagger.internal.DaggerGenerated;
import dagger.internal.Preconditions;

@DaggerGenerated
@SuppressWarnings({
        "unchecked",
        "rawtypes"
})
public final class DaggerBookComponent implements BookComponent {
    private final BookModule bookModule;

    private final DaggerBookComponent bookComponent = this;

    private DaggerBookComponent(BookModule bookModuleParam) {
        this.bookModule = bookModuleParam;

    }

    public static Builder builder() {
        //1 使用构造者模式实例化DaggerBookComponent对象
        return new Builder();
    }

    public static BookComponent create() {
        return new Builder().build();
    }

    @Override
    public void injectFirstActivity(FirstActivity activity) {
        injectFirstActivity2(activity);
    }

    @Override
    public void injectSecondActivity(SecondActivity activity) {
        injectSecondActivity2(activity);
    }

    private FirstActivity injectFirstActivity2(FirstActivity instance) {
        //4 Activity中的@Inject生成injectBook方法,通过BookModule_ProvideBookFactory来获取依赖注入对象
        FirstActivity_MembersInjector.injectBook(instance, BookModule_ProvideBookFactory.provideBook(bookModule));
        FirstActivity_MembersInjector.injectBook2(instance, BookModule_ProvideBookFactory.provideBook(bookModule));
        return instance;
    }

    private SecondActivity injectSecondActivity2(SecondActivity instance) {
        SecondActivity_MembersInjector.injectBook(instance, BookModule_ProvideBookFactory.provideBook(bookModule));
        return instance;
    }

    public static final class Builder {
        private BookModule bookModule;

        private Builder() {
        }

        //2 传入一个BookModule对象
        public Builder bookModule(BookModule bookModule) {
            this.bookModule = Preconditions.checkNotNull(bookModule);
            return this;
        }

        //3 实例化DaggerBookComponent
        public BookComponent build() {
            if (bookModule == null) {
                this.bookModule = new BookModule();
            }
            return new DaggerBookComponent(bookModule);
        }
    }
}
// Generated by Dagger (https://dagger.dev).
package cn.zhangmushui.dagger.demo1;

import dagger.internal.DaggerGenerated;
import dagger.internal.Factory;
import dagger.internal.Preconditions;
import dagger.internal.QualifierMetadata;
import dagger.internal.ScopeMetadata;

@ScopeMetadata
@QualifierMetadata
@DaggerGenerated
@SuppressWarnings({
    "unchecked",
    "rawtypes"
})
public final class BookModule_ProvideBookFactory implements Factory<Book> {
  private final BookModule module;

  public BookModule_ProvideBookFactory(BookModule module) {
    this.module = module;
  }

  @Override
  public Book get() {
    return provideBook(module);
  }

  public static BookModule_ProvideBookFactory create(BookModule module) {
    return new BookModule_ProvideBookFactory(module);
  }

  //5 通过BookModule拿到Book对象,并用Dagger2提供的Preconditions.checkNotNullFromProvides()方法来检查获取到的对象是否为空
  public static Book provideBook(BookModule instance) {
    return Preconditions.checkNotNullFromProvides(instance.provideBook());
  }
}
// Generated by Dagger (https://dagger.dev).
package cn.zhangmushui.dagger.demo1.activity;

import cn.zhangmushui.dagger.demo1.Book;
import dagger.MembersInjector;
import dagger.internal.DaggerGenerated;
import dagger.internal.InjectedFieldSignature;
import dagger.internal.QualifierMetadata;
import javax.inject.Provider;

@QualifierMetadata
@DaggerGenerated
@SuppressWarnings({
    "unchecked",
    "rawtypes"
})
public final class FirstActivity_MembersInjector implements MembersInjector<FirstActivity> {
  private final Provider<Book> bookProvider;

  private final Provider<Book> book2Provider;

  public FirstActivity_MembersInjector(Provider<Book> bookProvider, Provider<Book> book2Provider) {
    this.bookProvider = bookProvider;
    this.book2Provider = book2Provider;
  }

  public static MembersInjector<FirstActivity> create(Provider<Book> bookProvider,
      Provider<Book> book2Provider) {
    return new FirstActivity_MembersInjector(bookProvider, book2Provider);
  }

  @Override
  public void injectMembers(FirstActivity instance) {
    injectBook(instance, bookProvider.get());
    injectBook2(instance, book2Provider.get());
  }

  @InjectedFieldSignature("cn.zhangmushui.dagger.demo1.activity.FirstActivity.book")
  public static void injectBook(FirstActivity instance, Book book) {
    //6 将获取到的Book对象赋值给Activity中的book
    instance.book = book;
  }

  @InjectedFieldSignature("cn.zhangmushui.dagger.demo1.activity.FirstActivity.book2")
  public static void injectBook2(FirstActivity instance, Book book2) {
    instance.book2 = book2;
  }
}

以上几处注释,就是Dagger2的核心原理,也就是通过Dagger生成辅助类,在业务层需要获取依赖对象的地方使用@Inject去标注对象,就可以交给Dagger2的这些辅助类去实例化对象,起到了解耦的作用,提高了代码的可读性和可维护性。

类图如下,白色的是手动创建的类,灰色的是APT生成的类。

完整源码下载:https://gitee.com/mushuicode/dagger

关注木水小站 (zhangmushui.cn)和微信公众号【木水Code】,及时获取更多最新文章。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容