Android Dagger 2 框架的注解模块深入剖析 (一)

本人掘金号,欢迎点击关注:https://juejin.cn/user/4406498335701950

一、引言

在 Android 开发中,依赖注入(Dependency Injection,简称 DI)是一种强大的设计模式,它能够有效降低代码的耦合度,提高代码的可测试性和可维护性。Dagger 2 作为一个在 Android 和 Java 领域广泛应用的依赖注入框架,凭借其编译时生成代码的特性,避免了反射带来的性能开销,在性能和稳定性方面表现出色。而 Dagger 2 的注解模块则是整个框架的核心,它通过一系列注解来定义依赖关系、注入点以及组件等,使得开发者能够以声明式的方式配置依赖注入。本文将深入分析 Dagger 2 框架的注解模块,从源码级别详细解读每个注解的作用、实现原理以及使用场景。

二、依赖注入基础回顾

2.1 什么是依赖注入

依赖注入是一种设计模式,它允许对象在创建时接收其依赖项,而不是在对象内部创建依赖项。简单来说,就是将对象的依赖关系从对象本身的实现中分离出来,通过外部的方式提供给对象。这样做的好处是可以降低对象之间的耦合度,使得代码更加灵活和可维护。

例如,有一个 Car 类依赖于 Engine 类:

java

java 复制代码
// 传统方式,在 Car 类内部创建 Engine 实例
public class Car {
    private Engine engine;

    public Car() {
        this.engine = new Engine(); // 直接在构造函数中创建 Engine 实例
    }

    public void start() {
        engine.start();
    }
}

// Engine 类
public class Engine {
    public void start() {
        System.out.println("Engine started");
    }
}

在上述代码中,Car 类直接依赖于 Engine 类的具体实现,这使得 Car 类和 Engine 类之间的耦合度很高。如果需要更换 Engine 的实现,就需要修改 Car 类的代码。

而使用依赖注入的方式:

java

java 复制代码
// 使用依赖注入,通过构造函数传入 Engine 实例
public class Car {
    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine; // 通过构造函数注入 Engine 实例
    }

    public void start() {
        engine.start();
    }
}

// 使用时创建 Engine 实例并注入到 Car 中
public class Main {
    public static void main(String[] args) {
        Engine engine = new Engine();
        Car car = new Car(engine);
        car.start();
    }
}

通过依赖注入,Car 类不再直接依赖于 Engine 类的具体实现,而是依赖于 Engine 类的抽象(接口),这样可以在不修改 Car 类代码的情况下更换 Engine 的实现,提高了代码的灵活性和可维护性。

2.2 依赖注入的优点

  • 解耦:降低组件之间的耦合度,使得每个组件可以独立开发、测试和维护。
  • 可测试性:方便进行单元测试,因为可以通过注入模拟对象来测试组件的行为。
  • 可维护性:代码结构更加清晰,易于理解和修改。

三、Dagger 2 注解模块概述

3.1 Dagger 2 的工作原理

Dagger 2 是一个基于 Java 注解处理器的依赖注入框架,它在编译时扫描带有特定注解的类和方法,根据注解信息生成相应的代码,这些代码负责创建和管理依赖对象。在运行时,直接使用生成的代码来实现依赖注入,避免了反射带来的性能开销。

3.2 注解模块的核心作用

注解模块是 Dagger 2 的核心组成部分,它通过一系列注解来定义依赖关系、注入点以及组件等。开发者只需要在代码中使用这些注解进行声明,Dagger 2 的注解处理器就会在编译时自动生成实现依赖注入所需的代码。

3.3 主要注解介绍

Dagger 2 的注解模块包含了多个核心注解,下面是对这些注解的简要介绍:

  • @Inject:用于标记需要注入的构造函数、字段或方法。

  • @Module:用于定义提供依赖的模块类。

  • @Provides:用于标记模块类中的方法,这些方法负责创建和提供依赖对象。

  • @Component:用于定义组件接口,组件是连接依赖和注入点的桥梁。

  • @Singleton:用于标记单例对象,确保在整个应用生命周期中只创建一个实例。

接下来,我们将对每个注解进行详细的分析。

四、@Inject 注解

4.1 @Inject 注解的作用

@Inject 注解是 Dagger 2 中最基本的注解之一,它用于标记需要注入的构造函数、字段或方法。当 Dagger 2 遇到被 @Inject 注解的元素时,会尝试为其提供依赖。

4.2 构造函数注入

构造函数注入是最常用的注入方式之一,它通过在构造函数上使用 @Inject 注解来告诉 Dagger 2 如何创建对象。

java

java 复制代码
import javax.inject.Inject;

// Engine 类,使用 @Inject 注解构造函数
public class Engine {
    @Inject
    public Engine() {
        // 构造函数被 @Inject 注解,Dagger 2 可以创建 Engine 实例
    }

    public void start() {
        System.out.println("Engine started");
    }
}

// Car 类,使用 @Inject 注解构造函数注入 Engine 依赖
public class Car {
    private final Engine engine;

    @Inject
    public Car(Engine engine) {
        this.engine = engine; // 通过构造函数注入 Engine 实例
    }

    public void start() {
        engine.start();
    }
}

在上述代码中,Engine 类的构造函数被 @Inject 注解,这意味着 Dagger 2 可以创建 Engine 实例。Car 类的构造函数也被 @Inject 注解,并且接受一个 Engine 类型的参数,这表示 Car 类依赖于 Engine 类,Dagger 2 会在创建 Car 实例时自动注入 Engine 实例。

4.3 字段注入

字段注入是指在类的字段上使用 @Inject 注解,Dagger 2 会在对象创建后将依赖注入到这些字段中。

java

java 复制代码
import javax.inject.Inject;

// Car 类,使用 @Inject 注解字段注入 Engine 依赖
public class Car {
    @Inject
    Engine engine;

    public void start() {
        if (engine != null) {
            engine.start();
        }
    }
}

在上述代码中,Car 类的 engine 字段被 @Inject 注解,Dagger 2 会在创建 Car 实例后将 Engine 实例注入到该字段中。需要注意的是,字段注入通常需要在对象创建后手动调用注入方法,例如通过组件的 inject 方法。

4.4 方法注入

方法注入是指在类的方法上使用 @Inject 注解,Dagger 2 会在对象创建后调用该方法并注入依赖。

java

java 复制代码
import javax.inject.Inject;

// Car 类,使用 @Inject 注解方法注入 Engine 依赖
public class Car {
    private Engine engine;

    @Inject
    public void setEngine(Engine engine) {
        this.engine = engine; // 通过方法注入 Engine 实例
    }

    public void start() {
        if (engine != null) {
            engine.start();
        }
    }
}

在上述代码中,Car 类的 setEngine 方法被 @Inject 注解,Dagger 2 会在创建 Car 实例后调用该方法并注入 Engine 实例。

4.5 @Inject 注解的源码分析

@Inject 注解的定义位于 javax.inject 包中,其源码如下:

java

java 复制代码
package javax.inject;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 标记一个构造函数、字段或方法,Dagger 2 会尝试为其提供依赖。
 */
@Target({ CONSTRUCTOR, FIELD, METHOD })
@Retention(RUNTIME)
@Documented
public @interface Inject {
}

从源码可以看出,@Inject 注解是一个元注解,它可以应用于构造函数、字段和方法上。@Target 注解指定了 @Inject 注解可以应用的元素类型,@Retention 注解指定了注解的保留策略为运行时,这样 Dagger 2 的注解处理器可以在编译时获取到该注解信息。

4.6 @Inject 注解的使用注意事项

  • 构造函数注入优先:构造函数注入是最推荐的注入方式,因为它可以确保对象在创建时就已经完成了依赖注入,避免了对象在使用过程中出现空指针异常。
  • 字段注入和方法注入需要手动调用注入方法 :如果使用字段注入或方法注入,需要在对象创建后手动调用组件的 inject 方法来完成注入。
  • 循环依赖问题@Inject 注解无法解决循环依赖问题,即两个或多个对象相互依赖的情况。在这种情况下,需要使用其他方式来解决,例如使用 ProviderLazy

五、@Module 注解

5.1 @Module 注解的作用

@Module 注解用于定义提供依赖的模块类。模块类中的方法可以提供各种依赖对象,特别是当某些类无法使用 @Inject 注解构造函数时,或者需要对依赖对象进行自定义创建时,可以使用 @Module 来提供依赖。

5.2 模块类的定义

java

java 复制代码
import dagger.Module;
import dagger.Provides;

// 使用 @Module 注解定义一个模块类
@Module
public class CarModule {
    @Provides
    public Engine provideEngine() {
        return new Engine(); // 提供 Engine 实例
    }
}

在上述代码中,CarModule 类被 @Module 注解标记,这表示它是一个提供依赖的模块类。provideEngine 方法被 @Provides 注解标记,它负责创建并提供 Engine 实例。

5.3 模块类的使用

模块类通常需要与组件接口一起使用,组件接口通过 @Component 注解标记,并指定要使用的模块类。

java

java 复制代码
import dagger.Component;

// 使用 @Component 注解定义一个组件接口,并指定使用 CarModule
@Component(modules = {CarModule.class})
public interface CarComponent {
    Car getCar(); // 定义一个方法,用于获取 Car 实例
}

在上述代码中,CarComponent 接口被 @Component 注解标记,并指定使用 CarModule 模块类。getCar 方法用于获取 Car 实例。

5.4 @Module 注解的源码分析

@Module 注解的定义位于 dagger 包中,其源码如下:

java

java 复制代码
package dagger;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 标记一个类为模块类,该类中的方法可以提供依赖对象。
 */
@Target(TYPE)
@Retention(RUNTIME)
@Documented
public @interface Module {
    /**
     * 指定该模块依赖的其他模块类。
     */
    Class<?>[] includes() default {};

    /**
     * 指定该模块是否为子组件模块。
     */
    boolean subcomponents() default false;
}

从源码可以看出,@Module 注解是一个元注解,它可以应用于类上。includes 属性用于指定该模块依赖的其他模块类,subcomponents 属性用于指定该模块是否为子组件模块。

5.5 模块类的高级用法

5.5.1 模块依赖

模块类可以依赖其他模块类,通过 includes 属性指定。

java

java 复制代码
import dagger.Module;
import dagger.Provides;

// 定义一个 EngineModule 模块类
@Module
public class EngineModule {
    @Provides
    public Engine provideEngine() {
        return new Engine();
    }
}

// 定义一个 CarModule 模块类,依赖于 EngineModule
@Module(includes = {EngineModule.class})
public class CarModule {
    @Provides
    public Car provideCar(Engine engine) {
        return new Car(engine);
    }
}

在上述代码中,CarModule 模块类依赖于 EngineModule 模块类,这样 CarModule 就可以使用 EngineModule 提供的 Engine 实例。

5.5.2 子组件模块

模块类可以通过 subcomponents 属性指定为子组件模块,用于创建子组件。

java

java 复制代码
import dagger.Module;
import dagger.Subcomponent;

// 定义一个子组件接口
@Subcomponent
public interface WheelComponent {
    Wheel getWheel();

    @Subcomponent.Builder
    interface Builder {
        WheelComponent build();
    }
}

// 定义一个模块类,指定为子组件模块
@Module(subcomponents = {WheelComponent.class})
public class WheelModule {
    // 模块中的方法可以提供其他依赖
}

在上述代码中,WheelModule 模块类被指定为子组件模块,它包含了 WheelComponent 子组件。

六、@Provides 注解

6.1 @Provides 注解的作用

@Provides 注解用于标记模块类中的方法,这些方法负责创建和提供依赖对象。当 Dagger 2 需要某个依赖对象时,会调用相应的 @Provides 方法来获取该对象。

6.2 @Provides 方法的定义

java

java 复制代码
import dagger.Module;
import dagger.Provides;

// 使用 @Module 注解定义一个模块类
@Module
public class CarModule {
    @Provides
    public Engine provideEngine() {
        return new Engine(); // 提供 Engine 实例
    }

    @Provides
    public Car provideCar(Engine engine) {
        return new Car(engine); // 提供 Car 实例,并注入 Engine 依赖
    }
}

在上述代码中,provideEngine 方法和 provideCar 方法都被 @Provides 注解标记,分别负责提供 Engine 实例和 Car 实例。

6.3 @Provides 方法的参数

@Provides 方法可以接受参数,这些参数表示该方法依赖的其他对象。Dagger 2 会自动注入这些依赖对象。

java

java 复制代码
import dagger.Module;
import dagger.Provides;

// 使用 @Module 注解定义一个模块类
@Module
public class CarModule {
    @Provides
    public Engine provideEngine() {
        return new Engine();
    }

    @Provides
    public Car provideCar(Engine engine) {
        return new Car(engine); // 接受 Engine 实例作为参数
    }
}

在上述代码中,provideCar 方法接受一个 Engine 实例作为参数,Dagger 2 会自动注入 Engine 实例。

6.4 @Provides 注解的源码分析

@Provides 注解的定义位于 dagger 包中,其源码如下:

java

java 复制代码
package dagger;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 标记一个模块类中的方法,该方法负责提供依赖对象。
 */
@Target(METHOD)
@Retention(RUNTIME)
@Documented
public @interface Provides {
    /**
     * 指定该方法提供的依赖对象的类型。
     */
    Class<?>[] type() default {};
}

从源码可以看出,@Provides 注解是一个元注解,它可以应用于方法上。type 属性用于指定该方法提供的依赖对象的类型。

6.5 @Provides 方法的返回值

@Provides 方法的返回值类型表示该方法提供的依赖对象的类型。Dagger 2 会根据返回值类型来匹配依赖需求。

java

java 复制代码
import dagger.Module;
import dagger.Provides;

// 使用 @Module 注解定义一个模块类
@Module
public class CarModule {
    @Provides
    public Engine provideEngine() {
        return new Engine(); // 返回 Engine 实例
    }

    @Provides
    public Car provideCar(Engine engine) {
        return new Car(engine); // 返回 Car 实例
    }
}

在上述代码中,provideEngine 方法返回 Engine 实例,provideCar 方法返回 Car 实例。

6.6 @Provides 方法的作用域

@Provides 方法可以使用作用域注解来指定依赖对象的作用域,例如 @Singleton 注解。

java

java 复制代码
import dagger.Module;
import dagger.Provides;
import javax.inject.Singleton;

// 使用 @Module 注解定义一个模块类
@Module
public class CarModule {
    @Provides
    @Singleton
    public Engine provideEngine() {
        return new Engine(); // 提供单例 Engine 实例
    }

    @Provides
    public Car provideCar(Engine engine) {
        return new Car(engine);
    }
}

在上述代码中,provideEngine 方法使用了 @Singleton 注解,这表示该方法提供的 Engine 实例是单例的,在整个应用生命周期中只会创建一个实例。

七、@Component 注解

7.1 @Component 注解的作用

@Component 注解用于定义组件接口,组件是 Dagger 2 中连接依赖和注入点的桥梁。组件接口中定义的方法用于获取依赖对象,或者将依赖注入到目标对象中。

7.2 组件接口的定义

java

java 复制代码
import dagger.Component;

// 使用 @Component 注解定义一个组件接口,并指定使用 CarModule
@Component(modules = {CarModule.class})
public interface CarComponent {
    Car getCar(); // 定义一个方法,用于获取 Car 实例

    void inject(MainActivity activity); // 定义一个方法,用于将依赖注入到 MainActivity 中
}

在上述代码中,CarComponent 接口被 @Component 注解标记,并指定使用 CarModule 模块类。getCar 方法用于获取 Car 实例,inject 方法用于将依赖注入到 MainActivity 中。

7.3 组件接口的使用

组件接口通常需要在应用中创建实例,并通过实例来获取依赖对象或进行注入操作。

java

java 复制代码
// 创建 CarComponent 实例
CarComponent carComponent = DaggerCarComponent.create();

// 获取 Car 实例
Car car = carComponent.getCar();

// 将依赖注入到 MainActivity 中
MainActivity mainActivity = new MainActivity();
carComponent.inject(mainActivity);

在上述代码中,通过 DaggerCarComponent.create() 方法创建了 CarComponent 实例,然后可以使用该实例的 getCar 方法获取 Car 实例,使用 inject 方法将依赖注入到 MainActivity 中。

7.4 @Component 注解的源码分析

@Component 注解的定义位于 dagger 包中,其源码如下:

java

java 复制代码
package dagger;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 标记一个接口为组件接口,该接口负责连接依赖和注入点。
 */
@Target(TYPE)
@Retention(RUNTIME)
@Documented
public @interface Component {
    /**
     * 指定该组件使用的模块类。
     */
    Class<?>[] modules() default {};

    /**
     * 指定该组件依赖的其他组件接口。
     */
    Class<?>[] dependencies() default {};

    /**
     * 指定该组件的作用域注解。
     */
    Class<? extends java.lang.annotation.Annotation>[] scope() default {};
}

从源码可以看出,@Component 注解是一个元注解,它可以应用于接口上。modules 属性用于指定该组件使用的模块类,dependencies 属性用于指定该组件依赖的其他组件接口,scope 属性用于指定该组件的作用域注解。

7.5 组件的依赖关系

组件可以依赖其他组件,通过 dependencies 属性指定。

java

java 复制代码
import dagger.Component;

// 定义一个 EngineComponent 组件接口
@Component
public interface EngineComponent {
    Engine getEngine();
}

// 定义一个 CarComponent 组件接口,依赖于 EngineComponent
@Component(dependencies = {EngineComponent.class})
public interface CarComponent {
    Car getCar();
}

在上述代码中,CarComponent 组件接口依赖于 EngineComponent 组件接口,这样 CarComponent 就可以使用 EngineComponent 提供的 Engine 实例。

7.6 组件的作用域

组件可以使用作用域注解来指定其作用域,例如 @Singleton 注解。

java

java 复制代码
import dagger.Component;
import javax.inject.Singleton;

// 使用 @Singleton 注解指定 CarComponent 的作用域为单例
@Singleton
@Component(modules = {CarModule.class})
public interface CarComponent {
    Car getCar();
}

在上述代码中,CarComponent 组件接口使用了 @Singleton 注解,这表示该组件的作用域为单例,其提供的依赖对象也是单例的。

八、@Singleton 注解

8.1 @Singleton 注解的作用

@Singleton 注解用于标记单例对象,确保在整个应用生命周期中只创建一个实例。在 Dagger 2 中,@Singleton 注解通常与组件和 @Provides 方法一起使用。

8.2 @Singleton 注解在组件中的使用

java

java 复制代码
import dagger.Component;
import javax.inject.Singleton;

// 使用 @Singleton 注解指定 CarComponent 的作用域为单例
@Singleton
@Component(modules = {CarModule.class})
public interface CarComponent {
    Car getCar();
}

在上述代码中,CarComponent 组件接口使用了 @Singleton 注解,这表示该组件的作用域为单例,其提供的依赖对象也是单例的。

8.3 @Singleton 注解在 @Provides 方法中的使用

java

java 复制代码
import dagger.Module;
import dagger.Provides;
import javax.inject.Singleton;

// 使用 @Module 注解定义一个模块类
@Module
public class CarModule {
    @Provides
    @Singleton
    public Engine provideEngine() {
        return new Engine(); // 提供单例 Engine 实例
    }

    @Provides
    public Car provideCar(Engine engine) {
        return new Car(engine);
    }
}

在上述代码中,provideEngine 方法使用了 @Singleton 注解,这表示该方法提供的 Engine 实例是单例的,在整个应用生命周期中只会创建一个实例。

8.4 @Singleton 注解的源码分析

@Singleton 注解的定义位于 javax.inject 包中,其源码如下:

java

java 复制代码
package javax.inject;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.inject.Scope;

/**
 * 标记一个对象为单例对象,确保在整个应用生命周期中只创建一个实例。
 */
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Singleton {
}

从源码可以看出,@Singleton 注解是一个元注解,它继承自 @Scope 注解,表示该注解用于指定作用域。@Retention 注解指定了注解的保留策略为运行时,这样 Dagger 2 的注解处理器可以在编译时获取到该注解信息。

8.5 单例模式的实现原理

Dagger 2 在编译时会根据 @Singleton 注解生成相应的代码,实现单例模式。具体来说,Dagger 2 会在生成的组件实现类中维护一个单例对象的缓存,当需要获取单例对象时,会先从缓存中查找,如果缓存中存在则直接返回,否则创建新的实例并放入缓存中。

java

java 复制代码
// 简化的 Dagger 2 生成的组件实现类示例
public final class DaggerCarComponent implements CarComponent {
    private static DaggerCarComponent instance; // 单例实例

    private final CarModule carModule;
    private final Provider<Engine> engineProvider;
    private final Provider<Car> carProvider;

    private DaggerCarComponent(CarModule carModule) {
        this.carModule = carModule;
        this.engineProvider = SingletonProvider.create(CarModule_ProvideEngineFactory.create(carModule));
        this.carProvider = CarModule_ProvideCarFactory.create(carModule, engineProvider);
    }

    public static DaggerCarComponent create() {
        if (instance == null) {
            instance = new DaggerCarComponent(new CarModule());
        }
        return instance;
    }

    @Override
    public Car getCar() {
        return carProvider.get();
    }
}

在上述代码中,DaggerCarComponent 类维护了一个静态的 instance 变量,用于存储单例实例。create 方法会检查 instance 是否为空,如果为空则创建新

九、限定符注解(@Qualifier)

9.1 限定符注解的作用

在实际开发中,可能会存在同一类型的多个依赖对象。例如,在一个应用中可能有不同类型的 Engine,如 GasEngineElectricEngine。此时,仅通过类型无法区分这些依赖对象,就需要使用限定符注解来为依赖提供额外的标识。

9.2 定义限定符注解

java

java 复制代码
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

// 定义一个限定符注解 GasEngine
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface GasEngine {
}

// 定义一个限定符注解 ElectricEngine
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ElectricEngine {
}

这里定义了两个限定符注解 GasEngineElectricEngine,用于区分不同类型的 Engine

9.3 在 @Provides 方法中使用限定符注解

java

java 复制代码
import dagger.Module;
import dagger.Provides;

// 使用 @Module 注解定义一个模块类
@Module
public class EngineModule {
    @Provides
    @GasEngine
    public Engine provideGasEngine() {
        return new GasEngineImpl(); // 提供 GasEngine 实例
    }

    @Provides
    @ElectricEngine
    public Engine provideElectricEngine() {
        return new ElectricEngineImpl(); // 提供 ElectricEngine 实例
    }
}

EngineModule 中,通过 @GasEngine@ElectricEngine 限定符注解分别为 provideGasEngineprovideElectricEngine 方法提供的 Engine 实例进行了标识。

9.4 在注入点使用限定符注解

java

java 复制代码
import javax.inject.Inject;

public class Car {
    private final Engine engine;

    @Inject
    public Car(@GasEngine Engine engine) {
        this.engine = engine; // 注入 GasEngine 实例
    }

    public void start() {
        engine.start();
    }
}

Car 类的构造函数中,使用 @GasEngine 限定符注解指定要注入的是 GasEngine 实例。

9.5 限定符注解的源码分析

限定符注解本质上是一个自定义注解,它通过 @Qualifier 元注解进行标记。@Qualifier 注解的定义如下:

java

java 复制代码
package javax.inject;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 标记一个注解为限定符注解,用于区分同一类型的不同依赖对象。
 */
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
@Documented
public @interface Qualifier {
}

@Qualifier 注解只能应用于注解类型上,它的作用是告诉 Dagger 2 该注解是一个限定符注解,用于在注入时区分同一类型的不同依赖对象。

9.6 限定符注解的使用注意事项

  • 唯一性:限定符注解应该是唯一的,不同的依赖对象应该使用不同的限定符注解进行标识,避免混淆。
  • 一致性 :在 @Provides 方法和注入点使用限定符注解时,要确保注解的一致性,否则会导致依赖注入失败。

十、子组件注解(@Subcomponent)

10.1 子组件的作用

子组件是 Dagger 2 中用于组织和管理依赖关系的一种方式。当应用规模较大时,可能需要将依赖关系进行分层管理,子组件可以继承父组件的依赖,并添加自己的依赖,从而实现更细粒度的依赖管理。

10.2 定义子组件接口

java

java 复制代码
import dagger.Subcomponent;

// 定义一个子组件接口
@Subcomponent
public interface WheelComponent {
    Wheel getWheel();

    @Subcomponent.Builder
    interface Builder {
        WheelComponent build();
    }
}

在上述代码中,WheelComponent 是一个子组件接口,它定义了一个 getWheel 方法用于获取 Wheel 实例,同时定义了一个 Builder 接口用于构建子组件实例。

10.3 父组件中使用子组件

java

java 复制代码
import dagger.Component;

// 定义一个父组件接口
@Component
public interface CarComponent {
    Car getCar();
    WheelComponent.Builder wheelComponent(); // 提供子组件的构建器
}

CarComponent 父组件接口中,定义了一个 wheelComponent 方法,用于获取 WheelComponent 的构建器。

10.4 子组件模块

子组件通常需要一个对应的模块来提供自己的依赖。

java

java 复制代码
import dagger.Module;
import dagger.Provides;

// 定义一个子组件模块
@Module
public class WheelModule {
    @Provides
    public Wheel provideWheel() {
        return new Wheel(); // 提供 Wheel 实例
    }
}

WheelModule 模块中,通过 @Provides 方法提供了 Wheel 实例。

10.5 子组件的注入

java

java 复制代码
import javax.inject.Inject;

public class Car {
    private final Wheel wheel;

    @Inject
    public Car(Wheel wheel) {
        this.wheel = wheel;
    }

    public void drive() {
        wheel.rotate();
    }
}

Car 类中,可以通过构造函数注入 Wheel 实例,这个 Wheel 实例由子组件 WheelComponent 提供。

10.6 @Subcomponent 注解的源码分析

@Subcomponent 注解的定义如下:

java

java 复制代码
package dagger;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 标记一个接口为子组件接口,子组件可以继承父组件的依赖并添加自己的依赖。
 */
@Target(TYPE)
@Retention(RUNTIME)
@Documented
public @interface Subcomponent {
    /**
     * 指定该子组件使用的模块类。
     */
    Class<?>[] modules() default {};

    /**
     * 指定该子组件的作用域注解。
     */
    Class<? extends java.lang.annotation.Annotation>[] scope() default {};

    /**
     * 定义子组件的构建器接口。
     */
    @Target(TYPE)
    @Retention(RUNTIME)
    @Documented
    @interface Builder {}
}

@Subcomponent 注解可以应用于接口上,modules 属性用于指定子组件使用的模块类,scope 属性用于指定子组件的作用域注解,Builder 注解用于定义子组件的构建器接口。

10.7 子组件的生命周期

子组件的生命周期通常与父组件相关,但也可以有自己独立的生命周期。子组件可以在父组件的基础上创建,并且可以在需要时销毁。例如,在 Android 开发中,子组件可以与 Activity 或 Fragment 的生命周期绑定。

十一、Dagger 2 注解处理器分析

11.1 注解处理器的作用

Dagger 2 的注解处理器是整个框架的核心,它在编译时扫描所有带有 Dagger 2 注解的类和方法,根据注解信息生成相应的代码,这些代码负责创建和管理依赖对象。在运行时,直接使用生成的代码来实现依赖注入,避免了反射带来的性能开销。

11.2 核心注解处理器类

DaggerProcessor 是 Dagger 2 的核心注解处理器,它继承自 AbstractProcessor。以下是一个简化的 DaggerProcessor 示例:

java

java 复制代码
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;
import java.util.Set;

// 简化的 DaggerProcessor 示例
public class DaggerProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 处理注解逻辑
        // 扫描带有 @Inject、@Module、@Provides、@Component 等注解的类和方法
        // 根据注解信息生成相应的代码
        return true;
    }
}

process 方法中,注解处理器会扫描所有带有 Dagger 2 注解的类和方法,并根据注解信息生成相应的代码。

11.3 注解处理流程

11.3.1 扫描注解

注解处理器在编译时扫描所有带有 Dagger 2 注解的类和方法,通过 RoundEnvironment 对象获取这些注解信息。

java

java 复制代码
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    // 扫描带有 @Inject 注解的类
    Set<? extends Element> injectElements = roundEnv.getElementsAnnotatedWith(Inject.class);
    for (Element element : injectElements) {
        // 处理 @Inject 注解的元素
    }

    // 扫描带有 @Module 注解的类
    Set<? extends Element> moduleElements = roundEnv.getElementsAnnotatedWith(Module.class);
    for (Element element : moduleElements) {
        // 处理 @Module 注解的元素
    }

    // 扫描带有 @Provides 注解的方法
    Set<? extends Element> providesElements = roundEnv.getElementsAnnotatedWith(Provides.class);
    for (Element element : providesElements) {
        // 处理 @Provides 注解的元素
    }

    // 扫描带有 @Component 注解的接口
    Set<? extends Element> componentElements = roundEnv.getElementsAnnotatedWith(Component.class);
    for (Element element : componentElements) {
        // 处理 @Component 注解的元素
    }

    return true;
}
11.3.2 解析注解信息

注解处理器会解析 @Inject@Module@Provides@Component 等注解的信息,获取依赖关系和注入点等信息。

java

java 复制代码
// 解析 @Inject 注解的构造函数
if (element.getKind() == ElementKind.CONSTRUCTOR) {
    ExecutableElement constructor = (ExecutableElement) element;
    List<? extends VariableElement> parameters = constructor.getParameters();
    for (VariableElement parameter : parameters) {
        // 获取构造函数的参数类型
        TypeMirror parameterType = parameter.asType();
        // 处理参数类型
    }
}
11.3.3 生成代码

根据注解信息,注解处理器会生成相应的代码,如组件实现类、工厂类等。

java

java 复制代码
// 生成组件实现类
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(componentClassName);
try (Writer writer = sourceFile.openWriter()) {
    // 写入组件实现类的代码
    writer.write("public final class " + componentClassName + " implements " + componentInterfaceName + " {\n");
    // 生成组件实现类的具体代码
    writer.write("}\n");
} catch (IOException e) {
    e.printStackTrace();
}

11.4 注解处理器的性能优化

  • 增量编译支持:Dagger 2 的注解处理器支持增量编译,只处理发生变化的类和方法,提高编译效率。
  • 代码生成优化:注解处理器会对生成的代码进行优化,减少不必要的代码,提高运行时性能。

十二、注解模块的高级用法和技巧

12.1 多绑定(Multibindings)

多绑定允许一个类型有多个实现,并且可以将这些实现收集到一个集合中。Dagger 2 提供了 @IntoSet@IntoMap 等注解来支持多绑定。

12.1.1 @IntoSet 注解

java

java 复制代码
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;

import java.util.Set;

// 使用 @Module 注解定义一个模块类
@Module
public class AnimalModule {
    @Provides
    @IntoSet
    public Animal provideDog() {
        return new Dog(); // 提供 Dog 实例并添加到集合中
    }

    @Provides
    @IntoSet
    public Animal provideCat() {
        return new Cat(); // 提供 Cat 实例并添加到集合中
    }

    @Provides
    public Set<Animal> provideAnimals(Set<Animal> animals) {
        return animals; // 提供包含所有 Animal 实例的集合
    }
}

在上述代码中,provideDogprovideCat 方法使用 @IntoSet 注解将 DogCat 实例添加到一个集合中,provideAnimals 方法提供了包含所有 Animal 实例的集合。

12.1.2 @IntoMap 注解

java

java 复制代码
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;

import java.util.Map;

// 使用 @Module 注解定义一个模块类
@Module
public class FruitModule {
    @Provides
    @IntoMap
    @StringKey("apple")
    public Fruit provideApple() {
        return new Apple(); // 提供 Apple 实例并添加到 Map 中
    }

    @Provides
    @IntoMap
    @StringKey("banana")
    public Fruit provideBanana() {
        return new Banana(); // 提供 Banana 实例并添加到 Map 中
    }

    @Provides
    public Map<String, Fruit> provideFruits(Map<String, Fruit> fruits) {
        return fruits; // 提供包含所有 Fruit 实例的 Map
    }
}

在上述代码中,provideAppleprovideBanana 方法使用 @IntoMap@StringKey 注解将 AppleBanana 实例添加到一个 Map 中,provideFruits 方法提供了包含所有 Fruit 实例的 Map

12.2 懒加载(Lazy)

Lazy 是 Dagger 2 提供的一个接口,用于实现懒加载。当使用 Lazy 包装一个依赖对象时,该对象的创建会被延迟到第一次使用时。

java

java 复制代码
import javax.inject.Inject;
import javax.inject.Singleton;

import dagger.Lazy;

@Singleton
public class HeavyObject {
    public HeavyObject() {
        System.out.println("HeavyObject created");
    }

    public void doSomething() {
        System.out.println("HeavyObject is doing something");
    }
}

public class Client {
    private final Lazy<HeavyObject> heavyObjectLazy;

    @Inject
    public Client(Lazy<HeavyObject> heavyObjectLazy) {
        this.heavyObjectLazy = heavyObjectLazy;
    }

    public void useHeavyObject() {
        HeavyObject heavyObject = heavyObjectLazy.get(); // 第一次调用 get 方法时才创建 HeavyObject 实例
        heavyObject.doSomething();
    }
}

在上述代码中,Client 类通过构造函数注入了一个 Lazy<HeavyObject> 对象,当调用 useHeavyObject 方法时,第一次调用 heavyObjectLazy.get() 方法才会创建 HeavyObject 实例。

12.3 提供者(Provider)

Provider 是 Dagger 2 提供的一个接口,用于获取依赖对象的实例。与直接注入依赖对象不同,使用 Provider 可以在需要时多次获取依赖对象的实例。

java

java 复制代码
import javax.inject.Inject;
import javax.inject.Provider;

public class Printer {
    private final Provider<Message> messageProvider;

    @Inject
    public Printer(Provider<Message> messageProvider) {
        this.messageProvider = messageProvider;
    }

    public void printMessage() {
        Message message = messageProvider.get(); // 每次调用 get 方法都会获取一个新的 Message 实例
        System.out.println(message.getText());
    }
}

在上述代码中,Printer 类通过构造函数注入了一个 Provider<Message> 对象,每次调用 messageProvider.get() 方法都会获取一个新的 Message 实例。

十三、注解模块的性能优化

13.1 减少注解使用

不必要的注解会增加编译时间和代码复杂度,应尽量减少注解的使用。例如,如果一个类的构造函数没有依赖项,就不需要使用 @Inject 注解。

13.2 合理使用作用域

合理使用作用域注解(如 @Singleton)可以减少对象的创建次数,提高性能。对于那些在整个应用生命周期中只需要一个实例的对象,可以使用 @Singleton 注解。

13.3 避免循环依赖

循环依赖会导致依赖注入失败,并且可能会增加内存开销。在设计依赖关系时,应尽量避免循环依赖的出现。如果无法避免,可以使用 ProviderLazy 来解决循环依赖问题。

13.4 增量编译支持

Dagger 2 的注解处理器支持增量编译,只处理发生变化的类和方法。在开发过程中,使用增量编译可以提高编译效率。

十四、注解模块的测试

14.1 单元测试

可以使用 JUnit 和 Mockito 等工具对使用 Dagger 2 注解的类进行单元测试。

java

java 复制代码
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import javax.inject.Inject;

import static org.mockito.Mockito.verify;

// 待测试的类
public class Car {
    private final Engine engine;

    @Inject
    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.start();
    }
}

// 引擎接口
interface Engine {
    void start();
}

// 单元测试类
public class CarTest {
    @Mock
    private Engine mockEngine;

    private Car car;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        car = new Car(mockEngine);
    }

    @Test
    public void testStart() {
        car.start();
        verify(mockEngine).start(); // 验证 Engine 的 start 方法是否被调用
    }
}

在上述代码中,使用 Mockito 模拟了 Engine 对象,并对 Car 类的 start 方法进行了单元测试。

14.2 集成测试

在集成测试中,可以使用测试组件来提供测试依赖。

java

java 复制代码
import dagger.Component;
import javax.inject.Singleton;

// 测试组件接口
@Singleton
@Component(modules = {TestCarModule.class})
public interface TestCarComponent {
    Car getCar();
}

// 测试模块类
@Module
public class TestCarModule {
    @Provides
    @Singleton
    public Engine provideEngine() {
        return new TestEngine(); // 提供测试用的 Engine 实例
    }

    @Provides
    @Singleton
    public Car provideCar(Engine engine) {
        return new Car(engine);
    }
}

// 测试用的 Engine 类
class TestEngine implements Engine {
    @Override
    public void start() {
        System.out.println("Test engine started");
    }
}

// 集成测试类
public class CarIntegrationTest {
    @Test
    public void testCarIntegration() {
        TestCarComponent testCarComponent = DaggerTestCarComponent.create();
        Car car = testCarComponent.getCar();
        car.start();
    }
}

在上述代码中,定义了一个测试组件 TestCarComponent 和一个测试模块 TestCarModule,用于提供测试用的依赖。在集成测试中,创建测试组件实例并获取 Car 实例进行测试。

十五、总结

15.1 注解模块的优势

  • 解耦:通过注解明确依赖关系,降低组件之间的耦合度,使得每个组件可以独立开发、测试和维护。
  • 编译时检查:在编译时生成代码,能够提前发现依赖注入的问题,避免运行时出现错误。
  • 性能优化:避免了反射带来的性能开销,并且可以通过合理使用作用域注解和单例模式,减少对象的创建次数,提高应用性能。
  • 代码简洁:使用注解可以使代码更加简洁,提高代码的可读性和可维护性。

15.2 注解模块的局限性

  • 学习成本:Dagger 2 的注解和概念较多,对于初学者来说,学习成本较高。需要花费一定的时间来理解注解的作用和使用方法。
  • 编译时间:大量使用注解会增加编译时间,特别是在项目规模较大时,编译时间可能会成为一个问题。可以通过增量编译和优化注解使用来缓解这个问题。
  • 调试困难:由于 Dagger 2 在编译时生成大量的代码,当出现问题时,调试可能会比较困难。需要对 Dagger 2 的工作原理有深入的理解才能进行有效的调试。

15.3 未来发展趋势

随着 Android 开发技术的不断发展,Dagger 2 也在不断更新和完善。未来,Dagger 2 可能会进一步优化性能,减少编译时间,提供更多的高级特性和工具,以满足开发者的需求。同时,Dagger 2 可能会与其他流行的 Android 开发框架进行更紧密的集成,为开发者提供更加便捷的开发体验。

总之,Dagger 2 的注解模块是一个强大而灵活的工具,它为 Android 开发者提供了一种高效的方式来实现依赖注入。通过深入理解和合理使用注解模块,可以提高代码的质量和可维护性,为应用的开发和维护带来诸多好处。

相关推荐
may_一一3 小时前
docker操作镜像-以mysql为例
adb
鸿蒙布道师3 小时前
鸿蒙NEXT开发动画案例2
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
androidwork3 小时前
Kotlin Android工程Mock数据方法总结
android·开发语言·kotlin
xiangxiongfly9155 小时前
Android setContentView()源码分析
android·setcontentview
人间有清欢7 小时前
Android开发补充内容
android·okhttp·rxjava·retrofit·hilt·jetpack compose
人间有清欢8 小时前
Android开发报错解决
android
每次的天空9 小时前
Android学习总结之kotlin协程面试篇
android·学习·kotlin
didiplus10 小时前
MySQL 8.0 OCP(1Z0-908)英文题库(11-20)
数据库·mysql·adb·认证·ocp
每次的天空11 小时前
Android学习总结之Binder篇
android·学习·binder
峥嵘life11 小时前
Android 有线网开发调试总结
android