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

news2025/4/17 6:24:00

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

一、引言

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

二、依赖注入基础回顾

2.1 什么是依赖注入

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

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

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

// 使用依赖注入,通过构造函数传入 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// 创建 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

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

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

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

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

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

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

// 简化的 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

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

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

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

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

import dagger.Subcomponent;

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

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

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

10.3 父组件中使用子组件

java

import dagger.Component;

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

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

10.4 子组件模块

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

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

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

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

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

@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

// 解析 @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

// 生成组件实现类
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

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

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

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

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

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

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 开发者提供了一种高效的方式来实现依赖注入。通过深入理解和合理使用注解模块,可以提高代码的质量和可维护性,为应用的开发和维护带来诸多好处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2328425.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java全栈面试宝典:线程安全机制与Spring Boot核心原理深度解析

目录 一、Java线程安全核心原理 &#x1f525; 问题1&#xff1a;线程安全的三要素与解决方案 线程安全风险模型 线程安全三要素 synchronized解决方案 &#x1f525; 问题2&#xff1a;synchronized底层实现全解析 对象内存布局 Mark Word结构&#xff08;64位系统&…

Linux开发工具——apt

&#x1f4dd;前言&#xff1a; 在之前我们已经讲解了有关的Linux基础命令和Linux权限的问题&#xff0c;这篇文章我们来讲讲Linux的开发工具——apt。 &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&#xff1a;Linux &#x1f380;CSDN主页 愚润求学 …

嵌入式——Linux系统的使用以及编程练习

目录 一、Linux的进程、线程概念 &#xff08;一&#xff09;命令控制进程 1、命令查看各进程的编号pid 2、命令终止一个进程pid 二、初识Linux系统的虚拟机内存管理 &#xff08;一&#xff09;虚拟机内存管理 &#xff08;二&#xff09;与STM32内存管理对比 三、Lin…

在MacOS 10.15上使用MongoDB

这次是在MacOS 10.15上使用MongoDB。先在豆包问支持MacOS 10.15的MongoDB最新版是什么&#xff0c;答案是MongoDB 5.0。 抱着谨慎怀疑的态度去官方网站查询了一下&#xff0c;答案如下 MongoDB 7.x支持的最低版本MacOS是11MongoDB 6.x支持的最低版本MacOS是10.14 又找deepsee…

思二勋:未来所有的业务都将生于AI、长于AI、成于AI

每个时代都有其标志性的技术&#xff0c;每个技术的产生或极大地解放了个体的劳动力&#xff0c;提高了个体与组织之间的协作效率&#xff0c;或极大地促进了生产效率或使用体验&#xff0c;或将极大地优化了资源配置和供需匹配效率&#xff0c;从而提高人们的生活水平。从青铜…

混合专家模型(MoE):助力大模型实现高效计算

引言 近年来&#xff0c;大模型的参数规模不断攀升&#xff0c;如何在保证性能的前提下降低计算成本和显存消耗&#xff0c;成为业界关注的重点问题。混合专家模型&#xff08;Mixture of Experts, MoE&#xff09;应运而生&#xff0c;通过“分而治之”的设计理念&#xff0c…

【学习笔记】计算机网络(七)—— 网络安全

第7章 网络安全 文章目录 第7章 网络安全7.1 网络安全问题概述7.1.1 计算机网络面临的安全性威胁7.1.2 安全的计算机网络7.1.3 数据加密模型 7.2 两类密码体制7.2.1 对称密钥密码体制7.2.2 公钥密码体制 7.3 鉴别7.3.1 报文鉴别7.3.2 实体鉴别 7.4 密钥分配7.4.1 对称密钥的分配…

预测分析(四):面向预测分析的神经网络简介

文章目录 面向预测分析的神经网络简介神经网络模型1. 基本概念2. 前馈神经网络3. 常见激活函数4. 循环神经网络&#xff08;RNN&#xff09;5. 卷积神经网络&#xff08;CNN&#xff09; MPL结构工作原理激活函数训练方法 基于神经网络的回归——以钻石为例构建预测钻石价格的M…

LLaMA-Factory大模型微调全流程指南

该文档为LLaMA-Factory大模型微调提供了完整的技术指导&#xff0c;涵盖了从环境搭建到模型训练、推理和合并模型的全流程&#xff0c;适用于需要进行大模型预训练和微调的技术人员。 一、docker 容器服务 请参考如下资料制作 docker 容器服务&#xff0c;其中&#xff0c;挂…

为什么芯片半导体行业需要全星APQP系统?--行业研发项目管理软件系统

为什么芯片半导体行业需要全星APQP系统&#xff1f;--行业研发项目管理软件系统 在芯片半导体行业&#xff0c;严格的合规性要求、复杂的供应链协同及高精度质量管理是核心挑战。全星研发项目管理APQP系统专为高门槛制造业设计&#xff0c;深度融合APQP五大阶段&#xff08;从设…

vulkanscenegraph显示倾斜模型(5.6)-vsg::RenderGraph的创建

前言 上一章深入分析了vsg::CommandGraph的创建过程及其通过子场景遍历实现Vulkan命令录制的机制。本章将在该基础上&#xff0c;进一步探讨Vulkan命令录制中的核心封装——vsg::RenderGraph。作为渲染流程的关键组件&#xff0c;RenderGraph封装了vkCmdBeginRenderPass和vkCmd…

基于阿里云可观测产品构建企业级告警体系的通用路径与最佳实践

前言 1.1 日常生活中的告警 任何连续稳定运行的生产系统都离不开有效的监控与报警机制。通过监控&#xff0c;我们可以实时掌握系统和业务的运行状态&#xff1b;而报警则帮助我们及时发现并响应监控指标及业务中的异常情况。 在日常生活中&#xff0c;我们也经常遇到各种各样…

二叉树的ACM板子(自用)

package 二叉树的中序遍历;import java.util.*;// 定义二叉树节点 class TreeNode {int val; // 节点值TreeNode left; // 左子节点TreeNode right; // 右子节点// 构造函数TreeNode(int x) {val x;} }public class DMain {// 构建二叉树&#xff08;层序遍历方式&…

架构思维:查询分离 - 表数据量大查询缓慢的优化方案

文章目录 Pre引言案例何谓查询分离&#xff1f;何种场景下使用查询分离&#xff1f;查询分离实现思路1. 如何触发查询分离&#xff1f;方式一&#xff1a; 修改业务代码&#xff1a;在写入常规数据后&#xff0c;同步建立查询数据。方式二&#xff1a;修改业务代码&#xff1a;…

Qt进阶开发:QFileSystemModel的使用

文章目录 一、QFileSystemModel的基本介绍二、QFileSystemModel的基本使用2.1 在 QTreeView 中使用2.2 在 QListView 中使用2.3 在 QTableView 中使用 三、QFileSystemModel的常用API3.1 设置根目录3.2 过滤文件3.2.1 仅显示文件3.2.2 只显示特定后缀的文件3.2.3 只显示目录 四…

【C语言】字符串处理函数:strtok和strerror

在C语言中&#xff0c;字符串处理是编程的基础之一。本文将详细讲解两个重要的字符串处理函数&#xff1a;strtok和strerror 一、strtok函数 strtok函数用于将字符串分割成多个子串&#xff0c;这些子串由指定的分隔符分隔。其原型定义如下&#xff1a; char *strtok(char *s…

go语言:开发一个最简单的用户登录界面

1.用deepseek生成前端页面&#xff1a; 1.提问&#xff1a;请你用html帮我设计一个用户登录页面&#xff0c;要求特效采用科技感的背景渲染加粒子流动&#xff0c;用css、div、span标签&#xff0c;并给出最终合并后的代码。 生成的完整代码如下&#xff1a; <!DOCTYPE h…

《Maven高级应用:继承聚合设计与私服Nexus实战指南》

一、 Maven的继承和聚合 1.什么是继承 Maven 的依赖传递机制可以一定程度上简化 POM 的配置&#xff0c;但这仅限于存在依赖关系的项目或模块中。当一个项目的多个模块都依赖于相同 jar 包的相同版本&#xff0c;且这些模块之间不存在依赖关系&#xff0c;这就导致同一个依赖…

C语言数字分隔题目

一、题目引入 编写一个程序,打印出从用户输入的数字开始,递减到1的序列。要求每次打印一行,数字之间用逗号分隔,最后一个数字后面没有逗号。 二、代码展示 三、运行结果 四、思路分析 1.先用一个for循环对输入的数字进行递减 2.再对for循环里面的数字进行筛选 如果大于1 …

ARM-外部中断,ADC模数转换器

根据您提供的图片&#xff0c;我们可以看到一个S3C2440微控制器的中断处理流程图。这个流程图展示了从中断请求源到CPU的整个中断处理过程。以下是流程图中各个部分与您提供的寄存器之间的关系&#xff1a; 请求源&#xff08;带sub寄存器&#xff09;&#xff1a; 这些是具体的…