跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Dagger 2.38.1 注解详解:核心注解与使用规范

综述由AI生成基于 Dagger 2.38.1 版本深入解析了核心注解的使用规范。内容涵盖 @Scope 的作用域继承与生命周期控制,包括 Singleton、Reusable 等常见 Scope 的区别;@Qualifier 在解决类型歧义时的匹配机制及自定义规则;@IntoSet、@IntoMap 和 @ElementsIntoSet 在多绑定集合收集中的应用差异;以及 @MapKey 的 unwrapValue 配置策略。文章提供了详细的代码示例和最佳实践建议,帮助开发者避免常见错误,构建稳定高效的依赖注入系统。

www发布于 2025/2/6更新于 2026/6/926 浏览
Dagger 2.38.1 注解详解:核心注解与使用规范

Dagger 2.38.1 注解详解:核心注解与使用规范

前言

本文旨在深入解析 Dagger 2.38.1 版本中的核心注解,重点涵盖 @MapKey、@Scope、@Qualifier、@IntoSet、@IntoMap 和 @ElementsIntoSet。这些注解是构建依赖注入图的基础,理解它们的交互机制对于正确使用 Dagger 至关重要。

其中 @MapKey、@Scope、@Qualifier 三个注解仅可用于修饰其他注解(即元注解)。@MapKey 是 Dagger 自定义的注解,而 @Scope 和 @Qualifier 源自 javax.inject 包。

本文章节顺序经过精心编排,确保在讲解后续关联注解前,先介绍其前置依赖注解,以便读者建立完整的知识体系。

@Scope

@Scope 用于定义依赖对象的生命周期和作用范围。它必须修饰一个注解,该注解随后被用于标记组件或绑定方法。

@Scope 修饰的注解使用规则

  1. 节点限制:在同一个作用域节点上,使用 @Scope 修饰的注解最多只能出现一次。
  2. Module 限制:被 @Module 或 @ProducerModule 修饰的 Module 节点本身不允许直接使用 Scope 修饰的注解。
  3. Binding Method 限制:
    • 在 Module 中,使用 @Provides、@Binds、@Multibinds、@BindsOptionalOf 修饰的绑定方法允许同时使用 @Scope 修饰的注解。
    • 注意:@Produces、@Multibinds 和 @BindsOptionalOf 修饰的方法通常不建议或不允许同时使用 @Scope 修饰,具体取决于实现细节,标准实践中主要关注 @Provides 和 @Binds。
  4. 构造函数限制:
    • 被 @Inject 或 @AssistedInject 修饰的构造函数不允许直接同时使用 @Scope 修饰的注解。
    • 但是,包含 @Inject 构造函数的类所在的父级节点(如 Component)允许使用 @Scope 修饰的注解。
    • 包含 @AssistedInject 构造函数的类所在的父级节点不允许使用 @Scope 修饰的注解。
  5. Instance 限制:@BindsInstance 修饰的方法或其参数不允许同时使用 @Scope 修饰。

Scope 的作用域传递

@Scope 表示一个作用域,当前 Scope 修饰的注解修饰的节点所属的 currentComponent 节点及其往下的 Subcomponent 节点都可以使用该节点提供的实例。

以 @Singleton 为例:

@Module(subcomponents = {Subcomponent1.class, Subcomponent2.class, Subcomponent3.class})
   {
    
    
     A  {
          (b);
    }
}


   {
     ;
    
    SubcomponentX ; 
}

   {
    
     C c;
}


   {
    
      {}
}
public
class
Module
@Provides
@Singleton
public
bindingMethod
(B b)
return
new
A
@Component(modules = Module.class)
public
class
Component
void
inject
(MembersInjector m)
getSubcomponentX
()
// 这是一个 Subcomponent 节点
public
class
MembersInjector
@Inject
public
@Singleton
public
class
C
@Inject
public
C
()

实例解析:

  • 当前 @Singleton 和 @Provides 修饰的 bindingMethod 方法返回的 A 的实例化既可以在当前 Component 作用域中使用,也可以在 Subcomponent1、Subcomponent2、Subcomponent3 和 SubcomponentX 中使用(前提是这些子组件需要 A 的实例化对象)。
  • 当前 @Singleton 修饰的 C 类(因为 MembersInjector 类是待实例化的对象,所以对象中 @Inject 修饰的变量类型或普通方法返回类型)实例化既可以在当前 Component 作用域中使用,也可以在各个 Subcomponent 中使用。
  • 注意:如果在 Subcomponent1 中定义了 @Singleton 修饰的节点,那么该节点的实例对象不能反向在父级 Component 中使用。作用域是单向向下传递的。

常见的 Scope 注解

Dagger 2.38.1 版本源码中包含几个 Scope 修饰的注解:

  1. @Reusable:

    • 当 @Reusable 和 @Binds 同时修饰一个 Module 节点中的 bindingMethod 方法时:
      • 如果当前 bindingMethod 方法参数类型 A 节点没有使用 @Scope 修饰的注解,则直接使用 SingleCheck(单例获取)。
      • 如果 A 节点使用了 @Scope 修饰的注解,则使用 DoubleCheck(双重校验单例)。
    • SingleCheck 与 DoubleCheck 的区别在于后者不仅获取对象,还校验当前对象的实例化始终是同一个实例,线程安全性更高。
    • 示例代码:
      @Binds
      @Reusable
      CompilerOptions bindCompilerOptions(
          ProcessingEnvironmentCompilerOptions processingEnvironmentCompilerOptions
      );
      
    • 在其他情况下,@Reusable 的用法与 @Singleton 类似,都在作用域中进行 DoubleCheck 双重校验。
  2. @PerGeneratedFile:

    • 表示将当前修饰的节点及其往下关联的节点和当前所属的 currentComponent 生成的代码写在同一个文件中。在实际开发中较少使用,没有实际的业务意义。
  3. @PerComponentImplementation:

    • 主要用于 Dagger 内部处理代码。相当于一个 A 类,但是这个 A 可能包含自己,此时需要另外开辟一个用于生成子类 A 的实例,需要用到此注解。
  4. @ProductionScope:

    • 如果使用了 @ProductionComponent 或 @ProductionSubcomponent 修饰的 Component 节点,Dagger 会为我们生成一个 @Module 修饰的 Module 节点。
    • 该 Module 节点中包含 monitor 方法,用于监控整个 Production Component 的生命周期。
    • 示例代码:
      @Provides
      @ProductionScope
      static ProductionComponentMonitor monitor(
          Provider<component节点类型> component,
          Provider<Set<ProductionComponentMonitor.Factory>> factories
      ) {
          return Monitors.createMonitorForComponent(component, factories);
      }
      
    • @ProductionScope 的实际作用仍是当前 Component 的作用域,但增加了监控功能。

@Qualifier

@Qualifier(限定符)用于解决依赖注入过程中类型匹配歧义的问题。它必须修饰一个注解。

@Qualifier 修饰的注解使用规则

  1. 节点限制:节点上只允许出现一个 @Qualifier 修饰注解的注解。
  2. Component Method 限制:Component Method 只有在返回类型是 Subcomponent 节点或 Subcomponent.Creator 节点情况下才允许使用 Qualifier 修饰的注解修饰。
  3. Binding Method 限制:bindingMethod 方法可以被 Qualifier 修饰的注解修饰。
  4. 构造函数限制:
    • 被 @Inject 或 @AssistedInject 修饰的构造方法不允许使用 Qualifier 修饰的注解修饰。
    • 但是,构造函数的参数可以使用 Qualifier 修饰的注解。
  5. Assisted 限制:@Assisted 修饰的参数节点不能被 Qualifier 注解修饰的注解修饰。
  6. MembersInjector 限制:MembersInjector 的成员注入 T 类型不允许使用 Qualifier 修饰的注解修饰。
  7. AssistedInject 兼容性:一个节点允许使用 Qualifier 修饰,那么当前节点类型(如果是方法,则表示方法返回类型)的构造函数允许使用 @AssistedInject 修饰。

使用场景

@Qualifier 修饰的注解主要是为了在实例化过程中成功匹配到正确的参数。当存在多个相同类型的依赖时,必须依靠限定符来精确匹配。

示例代码:

@Retention(RUNTIME)
@Qualifier
public @interface ProcessingOptions {}

public final class ExternalBindingGraphPlugins {
    @Inject
    ExternalBindingGraphPlugins(...,
        @ProcessingOptions Map<String, String> processingOptions) {
    }
}

@Module
interface ProcessingEnvironmentModule {
    @Provides
    @ProcessingOptions
    static Map<String, String> processingOptions(XProcessingEnv xProcessingEnv) {
        return xProcessingEnv.getOptions();
    }

    @Provides
    static Map<String, String> getMap(String str) {
        return new HashMap<>();
    }
}

解析: ExternalBindingGraphPlugins 对象的实例化需要 Map<String, String> 对象。这里有两个 Map<String, String> 提供者,如果不加区分,Dagger 将无法确定使用哪一个。通过 @ProcessingOptions 精确匹配,可以明确指定使用哪个提供者。

在容易产生类型混淆的情况下,务必使用 @Qualifier 修饰的注解。

@IntoSet、@IntoMap 和 @ElementsIntoSet

这三个注解用于多绑定(Multibinding),收集同一种类型的对象集合。

通用规则

  1. 互斥性:不允许同时使用,一个方法上仅仅可以使用 @IntoSet、@IntoMap 和 @ElementsIntoSet 中的一个。
  2. 位置限制:必须在 bindingMethod 绑定方法上使用,并且只支持 @Provides、@Produces 或 @Binds 三种类型的 bindingMethod 方法。
  3. 返回值限制:如果使用了 @ElementsIntoSet,那么当前 bindingMethod 方法返回类型必须是 Set<T>。
  4. 成对出现:@IntoMap 和 @MapKey 修饰的注解一定是成对出现的。

@IntoSet

@IntoSet 用于收集同一种类型的对象到一个 Set 集合中。

示例代码:

@Module
public interface BindingMethodValidatorsModule {
    // 下面的都是该方法参数
    @Provides
    static ImmutableMap<ClassName, BindingMethodValidator> indexValidators(
            Set<BindingMethodValidator> validators) {
        return uniqueIndex(validators, BindingMethodValidator::methodAnnotation);
    }

    @Binds
    @IntoSet
    BindingMethodValidator provides(ProvidesMethodValidator validator);

    @Binds
    @IntoSet
    BindingMethodValidator produces(ProducesMethodValidator validator);

    @Binds
    @IntoSet
    BindingMethodValidator binds(BindsMethodValidator validator);

    @Binds
    @IntoSet
    BindingMethodValidator multibinds(MultibindsMethodValidator validator);

    @Binds
    @IntoSet
    BindingMethodValidator bindsOptionalOf(BindsOptionalOfMethodValidator validator);
}

解析: Set<BindingMethodValidator> validators 用于收集所有的 BindingMethodValidator 类型对象。@Binds 修饰的 bindingMethod 方法有且仅有一个参数,并且参数类型一定是方法返回类型或返回类型子类。

延伸:这里的 @Binds 当然可以改成 @Provides。

@ElementsIntoSet

和 @IntoSet 用法基本一致,但 @ElementsIntoSet 返回类型必须是 Set<T>。

示例代码:

@Module
public interface BindingMethodValidatorsModule {
    @Provides
    static ImmutableMap<ClassName, BindingMethodValidator> indexValidators(
            Set<BindingMethodValidator> validators) {
        return uniqueIndex(validators, BindingMethodValidator::methodAnnotation);
    }

    @Binds
    @ElementsIntoSet
    Set<BindingMethodValidator> provides(ProvidesMethodValidator validator);

    @Binds
    @ElementsIntoSet
    Set<BindingMethodValidator> produces(ProducesMethodValidator validator);

    @Binds
    @ElementsIntoSet
    Set<BindingMethodValidator> binds(BindsMethodValidator validator);

    @Binds
    @ElementsIntoSet
    Set<BindingMethodValidator> multibinds(MultibindsMethodValidator validator);

    @Binds
    @ElementsIntoSet
    Set<BindingMethodValidator> bindsOptionalOf(BindsOptionalOfMethodValidator validator);
}

区别: 还有一个关键区别,@ElementsIntoSet 可以返回空绑定,但是 @IntoSet 不会。基本 @IntoSet 用的比较多,@ElementsIntoSet 感觉像是一个附带产品,但在特定场景下(如可选的多绑定)非常有用。

@IntoMap

@IntoMap 也是收集同一类对象,但是 @IntoMap 和 @MapKey 修饰的注解是成双成对出现的。

原理: @IntoMap 是为了将当前类型 T 收集到 Map<K,T> 中,那么必须由 @MapKey 修饰的注解提供 K。

示例代码:

@Beta
@Module
public abstract class AndroidInjectionModule {
    @Multibinds
    abstract Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>
        activityInjectorFactories();
}
@Module(subcomponents = MainActivityModule_ContributeMainActivity.MainActivitySubcomponent.class)
public abstract class MainActivityModule_ContributeMainActivity {
    private MainActivityModule_ContributeMainActivity() {}

    @Binds
    @IntoMap
    @ActivityKey(MainActivity.class)
    abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
        MainActivitySubcomponent.Builder builder);

    @Subcomponent(modules = FragmentBuildersModule.class)
    public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
        @Subcomponent.Builder
        abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
    }
}

解析: AndroidInjectionModule 类的 activityInjectorFactories 方法用于收集 Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>。 MainActivityModule_ContributeMainActivity 类中的 bindAndroidInjectorFactory 方法 @ActivityKey(MainActivity.class) 提供 K:MainActivity.class,V:MainActivitySubcomponent.Builder,符合被收集的条件。

@MapKey

当前使用 @MapKey 修饰的注解,满足如下规则:

  1. unwrapValue = true (默认):

    • 被修饰的注解有且仅有一个方法。
    • 该方法返回类型不能是数组类型。
    • 例如:ClassKey 注解。
  2. unwrapValue = false:

    • 被修饰的注解方法不但不限制,而且项目中必须引入了 com.google.auto.value.AutoAnnotation 依赖。
    • 引入 AutoAnnotation 的目的:对 MapKey 修饰的注解的所有方法返回类型作为变量生成一个新的类 T,相当于 @MapKey.unwrapValue() = true 情况下的 @MapKey(T.class)。
    • 建议:MapKey.unwrapValue() = false 可以使用 MapKey.unwrapValue() = true 替代,而且 true 更容易理解、实现也更加稳健。非必要不要使用 false。
  3. 关联性:@MapKey 和 @IntoMap 是情侣关系,必须在一起使用。

最佳实践与常见问题

1. 避免 Scope 冲突

在大型项目中,不同模块可能会定义不同的 Scope。请确保在合并 Module 时,Scope 的定义不会发生冲突。如果一个组件已经定义了 @Singleton,不要在子组件中再次定义同名的 Scope 注解,除非意图是覆盖父级行为。

2. Qualifier 命名规范

自定义 @Qualifier 注解时,建议遵循 @Name 或 @Type 的命名模式,使其语义清晰。例如 @ProcessingOptions 比 @Option 更明确。

3. Multibinding 的空值处理

在使用 @IntoSet 或 @IntoMap 时,如果某个绑定方法返回 null,可能会导致运行时错误。建议使用 @BindsOptionalOf 来处理可选的多绑定项。

4. MapKey 的性能

@MapKey 的 unwrapValue 为 false 时虽然灵活,但会增加编译期的复杂度。除非必要,优先选择 unwrapValue = true 的配置,以保证编译速度和稳定性。

总结

本文详细解析了 Dagger 2.38.1 中的核心注解机制。通过理解 @Scope 的生命周期管理、@Qualifier 的类型匹配、以及 @IntoSet/Map 的多绑定收集逻辑,开发者可以更高效地构建复杂的依赖注入图。在实际开发中,应严格遵守各注解的使用规则,避免常见的配置错误,以确保应用启动时的稳定性和运行时的正确性。

目录

  1. Dagger 2.38.1 注解详解:核心注解与使用规范
  2. 前言
  3. @Scope
  4. @Scope 修饰的注解使用规则
  5. Scope 的作用域传递
  6. 常见的 Scope 注解
  7. @Qualifier
  8. @Qualifier 修饰的注解使用规则
  9. 使用场景
  10. @IntoSet、@IntoMap 和 @ElementsIntoSet
  11. 通用规则
  12. @IntoSet
  13. @ElementsIntoSet
  14. @IntoMap
  15. @MapKey
  16. 最佳实践与常见问题
  17. 1. 避免 Scope 冲突
  18. 2. Qualifier 命名规范
  19. 3. Multibinding 的空值处理
  20. 4. MapKey 的性能
  21. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 滑动窗口算法:查找字符串中的字母异位词
  • Python 数据挖掘实战:回归、分类、聚类与关联分析详解
  • Claude Code 与 ChatGPT、Copilot 的核心差异
  • 深度解读国产大模型Kimi:功能、使用与背景分析
  • Claude Code 与 ChatGPT、Copilot 的本质区别
  • ChatGLM-6B 智能写作助手开发指南
  • 职场人如何利用 AI 提升效率与创造副业收入
  • Python 安装后缺失 pip 的解决方案
  • iOS 26 Liquid Glass TabBar 适配实战与实现
  • Java 21 虚拟线程压测全记录与性能分析
  • GitHub Copilot Pro 学生免费认证与 VS Code 集成指南
  • Android 内存优化指南:数据结构与 5R 法则
  • HarmonyOS Next DevEco Studio 使用指南:添加 Ability 与服务卡片
  • Django REST Framework 企业级 API 架构实战
  • C++ 递归算法实战:汉诺塔问题详解
  • Windows 部署 OpenClaw 接入飞书机器人
  • Python 3.12 安装库报错:pkgutil.ImpImporter 缺失
  • whisper-large-v3-turbo 高效部署与性能解析
  • APE 自动化指令集构建与优化实践
  • CoPaw Windows 安装与应用实战指南

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online