Dagger2 依赖注入深度解析:编译时注解处理与依赖图生成

简介: Dagger2 是 Google 推出的编译时依赖注入框架,通过 APT 在编译期生成高效、无反射的 Java/Kotlin 代码,构建并验证依赖图(DAG),实现解耦、可测试与作用域管控,兼顾性能与工程可维护性。

Dagger2 依赖注入深度解析:编译时注解处理与依赖图生成

Dagger2 是 Google 推出的编译时依赖注入框架,相比运行时反射的注入方案,它通过 APT(Annotation Processing Tool)在编译期生成代码,避免了反射带来的性能开销。本文从注解处理器、依赖图生成、模块化设计三个层面,深入解析 Dagger2 的核心原理。


为什么需要依赖注入

传统依赖管理的问题

class UserRepository(private val api: ApiService) {
    suspend fun getUser(id: String) = api.fetchUser(id)
}

class UserViewModel {
    private val retrofit = Retrofit.Builder()
        .baseUrl("https://api.example.com")
        .build()
    private val api = retrofit.create(ApiService::class.java)
    private val repository = UserRepository(api)

    fun loadUser(id: String) = repository.getUser(id)
}

痛点:

  • 强耦合:ViewModel 直接创建 Retrofit 实例,无法替换实现
  • 难测试:无法注入 Mock 对象进行单元测试
  • 生命周期混乱:每个 ViewModel 都创建新的 Retrofit 实例

Dagger2 的解决方案

@Module
class NetworkModule {
    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit = Retrofit.Builder()
        .baseUrl("https://api.example.com")
        .build()

    @Provides
    fun provideApiService(retrofit: Retrofit): ApiService =
        retrofit.create(ApiService::class.java)
}

class UserViewModel @Inject constructor(
    private val repository: UserRepository
) {
    fun loadUser(id: String) = repository.getUser(id)
}

优势:

  • 解耦:ViewModel 只声明需要什么,不关心怎么创建
  • 可测试:通过 Module 替换为 Mock 实现
  • 单例管理:@Singleton 确保全局唯一

Dagger2 核心原理

1. APT 注解处理器

Dagger2 通过 dagger-compiler 在编译期扫描注解,生成工厂类和注入器:

// 源代码
@Module
class AppModule {
    @Provides
    fun provideContext(app: Application): Context = app.applicationContext
}

@Component(modules = [AppModule::class])
interface AppComponent {
    fun inject(activity: MainActivity)
}

编译后生成的代码(简化版):

// DaggerAppComponent.java
public final class DaggerAppComponent implements AppComponent {
   
    private Provider<Context> contextProvider;

    private DaggerAppComponent(Application app) {
   
        this.contextProvider = AppModule_ProvideContextFactory.create(
            new AppModule(), app
        );
    }

    @Override
    public void inject(MainActivity activity) {
   
        injectMainActivity(activity);
    }

    private void injectMainActivity(MainActivity activity) {
   
        MainActivity_MembersInjector.injectRepository(
            activity, repositoryProvider.get()
        );
    }
}

关键点:

  • 生成的 DaggerAppComponent 实现了 AppComponent 接口
  • Provider<T> 是工厂模式的封装,按需创建对象
  • MembersInjector 负责将依赖注入到目标类的字段

2. 依赖图生成

Dagger2 在编译期构建依赖图(DAG),检查循环依赖和缺失依赖:

@Module
class DataModule {
    @Provides
    fun provideDatabase(): AppDatabase = Room.databaseBuilder(...)

    @Provides
    fun provideUserDao(db: AppDatabase): UserDao = db.userDao()
}

class UserRepository @Inject constructor(
    private val dao: UserDao,
    private val api: ApiService
)

依赖关系图:

AppComponent
  └─ UserRepository
       ├─ UserDao
       │   └─ AppDatabase (from DataModule)
       └─ ApiService (from NetworkModule)

编译期检查:

  • 如果 AppDatabase 没有被 @Provides 提供,编译失败
  • 如果存在 A -> B -> C -> A 的循环依赖,报错

3. Scope 作用域管理

Dagger2 通过 Scope 注解管理对象生命周期:

@Singleton
@Component(modules = [AppModule::class])
interface AppComponent {
    fun provideUserRepository(): UserRepository
}

@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope

@ActivityScope
@Subcomponent
interface ActivityComponent {
    fun provideViewModel(): UserViewModel
}

生命周期映射:

  • @Singleton → 跟随 AppComponent 生命周期(全局单例)
  • @ActivityScope → 跟随 ActivityComponent 生命周期(Activity 级单例)

实现原理:

// 生成的单例缓存
public final class DaggerAppComponent {
   
    private volatile Provider<UserRepository> repositoryProvider;

    private UserRepository getUserRepository() {
   
        if (repositoryProvider == null) {
   
            synchronized (this) {
   
                if (repositoryProvider == null) {
   
                    repositoryProvider = DoubleCheck.provider(
                        new UserRepository_Factory(daoProvider, apiProvider)
                    );
                }
            }
        }
        return repositoryProvider.get();
    }
}

关键:DoubleCheck.provider() 实现了双重检查锁,确保线程安全的单例。


实战:多模块依赖注入

场景:App + Feature 模块

// :app module
@Singleton
@Component(modules = [AppModule::class, NetworkModule::class])
interface AppComponent {
    fun userComponent(): UserComponent.Factory
}

// :feature-user module
@Subcomponent(modules = [UserModule::class])
@ActivityScope
interface UserComponent {
    fun inject(activity: UserActivity)

    @Subcomponent.Factory
    interface Factory {
        fun create(): UserComponent
    }
}

@Module
class UserModule {
    @Provides
    @ActivityScope
    fun provideViewModel(repository: UserRepository): UserViewModel =
        UserViewModel(repository)
}

注入流程

  1. App 启动时创建 AppComponent

    class MyApplication : Application() {
     lateinit var appComponent: AppComponent
    
     override fun onCreate() {
         super.onCreate()
         appComponent = DaggerAppComponent.builder()
             .appModule(AppModule(this))
             .build()
     }
    }
    
  2. Activity 创建 UserComponent

    class UserActivity : AppCompatActivity() {
     @Inject lateinit var viewModel: UserViewModel
    
     override fun onCreate(savedInstanceState: Bundle?) {
         (application as MyApplication).appComponent
             .userComponent()
             .create()
             .inject(this)
         super.onCreate(savedInstanceState)
     }
    }
    

依赖关系:

  • UserViewModel 需要 UserRepository
  • UserRepository 由 AppComponent 提供(跨模块共享)
  • UserViewModel 由 UserComponent 提供(Activity 级)

进阶技巧

1. Lazy 注入延迟初始化

class MainActivity : AppCompatActivity() {
    @Inject lateinit var database: Lazy<AppDatabase>

    override fun onCreate(savedInstanceState: Bundle?) {
        component.inject(this)
        super.onCreate(savedInstanceState)

        // 只有在第一次调用 get() 时才创建数据库
        val db = database.get()
    }
}

2. Provider 多次获取新实例

class ImageLoader @Inject constructor(
    private val bitmapProvider: Provider<Bitmap>
) {
    fun loadMultipleImages() {
        val bitmap1 = bitmapProvider.get()  // 新实例
        val bitmap2 = bitmapProvider.get()  // 另一个新实例
    }
}

3. Multibindings 集合注入

@Module
interface PluginModule {
    @Binds
    @IntoSet
    fun bindGlidePlugin(impl: GlidePluginImpl): Plugin

    @Binds
    @IntoSet
    fun bindOkHttpPlugin(impl: OkHttpPluginImpl): Plugin
}

class PluginManager @Inject constructor(
    private val plugins: Set<@JvmSuppressWildcards Plugin>
) {
    fun initialize() {
        plugins.forEach { it.init() }
    }
}

与 Hilt 的对比

维度 Dagger2 Hilt
配置复杂度 需要手动定义 Component 自动生成 Component
Android 集成 需要手动注入 支持 @AndroidEntryPoint
ViewModel 注入 需要自定义 Factory 原生支持 @HiltViewModel
适用场景 纯 Kotlin/Java 项目 Android 项目优先

Hilt 本质是 Dagger2 的上层封装,底层仍然使用 Dagger2 的 APT 生成代码。


常见问题排查

1. 编译报错:Missing binding

error: [Dagger/MissingBinding] ApiService cannot be provided

原因:ApiService 没有被 @Provides 或 @Inject 标记。

解决:

@Module
class NetworkModule {
    @Provides
    fun provideApiService(retrofit: Retrofit): ApiService =
        retrofit.create(ApiService::class.java)
}

2. 循环依赖

error: [Dagger/DependencyCycle] Found a dependency cycle:
  A -> B -> C -> A

解决方案:

  • 使用 Lazy<T> 打破循环
  • 重新设计依赖关系

3. Scope 不匹配

@Singleton
class AppRepository

@ActivityScope
class UserViewModel @Inject constructor(
    private val repository: AppRepository  // ❌ Scope 不匹配
)

规则:子 Component 可以依赖父 Component 的 Singleton 对象,但不能反向依赖。


总结

Dagger2 通过编译期代码生成实现依赖注入,核心优势在于:

  1. 零运行时开销:APT 生成的代码直接调用构造函数,无反射
  2. 编译期检查:依赖缺失和循环依赖在编译期就能发现
  3. 作用域管理:Scope 注解清晰定义对象生命周期

对于 Android 项目,建议优先使用 Hilt,享受自动化配置和 Android 组件集成的便利;对于纯 Kotlin/Java 项目或需要精细控制的场景,Dagger2 仍然是最佳选择。

理解 Dagger2 的原理,也是掌握现代 Android 架构设计的重要一环。

相关文章
|
3月前
|
存储 自然语言处理 测试技术
[鸿蒙从零到一] HarmonyOS 通知与提醒实战:消息发布、点击跳转与定时触达
本文详解鸿蒙HarmonyOS通知与提醒实战:区分NotificationKit(即时通知)与ReminderAgent(定时提醒),涵盖权限申请、通知槽管理、点击跳转、进度更新、定时触发、ID去重、错误处理及真机测试要点,助开发者构建稳定可靠的生产级消息触达能力
574 0
|
缓存 编解码 Android开发
Android内存优化之图片优化
本文主要探讨Android开发中的图片优化问题,包括图片优化的重要性、OOM错误的成因及解决方法、Android支持的图片格式及其特点。同时介绍了图片储存优化的三种方式:尺寸优化、质量压缩和内存重用,并详细讲解了相关的实现方法与属性。此外,还分析了图片加载优化策略,如异步加载、缓存机制、懒加载等,并结合多级缓存流程提升性能。最后对比了几大主流图片加载框架(Universal ImageLoader、Picasso、Glide、Fresco)的特点与适用场景,重点推荐Fresco在处理大图、动图时的优异表现。这些内容为开发者提供了全面的图片优化解决方案。
618 1
|
存储 缓存 NoSQL
跟着源码学IM(十一):一套基于Netty的分布式高可用IM详细设计与实现(有源码)
本文将要分享的是如何从零实现一套基于Netty框架的分布式高可用IM系统,它将支持长连接网关管理、单聊、群聊、聊天记录查询、离线消息存储、消息推送、心跳、分布式唯一ID、红包、消息同步等功能,并且还支持集群部署。
14196 1
|
3月前
|
缓存 监控 数据挖掘
Android ANR 定位与治理:从主线程阻塞到线上证据闭环
本文系统解析Android ANR成因与治理:厘清“未响应”非崩溃本质,聚焦主线程阻塞根因(锁竞争、I/O、Binder等),强调通过堆栈+Trace+指标构建线上证据闭环,并提供典型问题修复方案与工程化治理实践。
299 0
|
3月前
|
存储 缓存 API
[鸿蒙从零到一] HarmonyOS 后台任务与定时能力实战:短时任务、长时任务与延迟调度
本文系统讲解HarmonyOS后台任务实战,涵盖短时任务(收尾保护)、长时任务(用户可感知持续运行)、延迟任务(Work Scheduler按条件调度)及代理提醒(准时通知),结合ArkTS代码、权限配置、异常处理与测试要点,助开发者构建稳定、合规、低耗的后台能力
334 0
|
3月前
|
测试技术 Android开发 iOS开发
Multiplatform:用一套声明式 UI 连接多端
Compose Multiplatform 是基于 Kotlin 的跨平台 UI 框架,延续 Jetpack Compose 声明式范式,支持 Android、iOS、桌面与 Web。它共享 UI 逻辑与状态,分层处理平台能力,适合工具类、中后台等 UI 高度一致的场景,助力 Android 团队高效拓展多端。(239 字)
259 0
|
2月前
|
JSON 安全 API
[Android 从零到一] Android 深度链接与 App Links:从 URI Scheme 到可验证的应用跳转
本文详解Android深度链接技术演进:从易被劫持的URI Scheme,到基于数字资产链接(assetlinks.json)验证的App Links方案。涵盖Manifest配置、签名指纹获取、验证流程、Navigation集成及Android 12适配,并总结常见坑点与最佳实践,助你实现安全、无缝的应用内跳转
209 0
|
4月前
|
定位技术 Android开发 Kotlin
Service 与前台服务:让任务在后台持续运行
Android Service 是无UI后台组件,专用于长期任务。自8.0起,需用前台服务(带常驻通知)保障运行;核心要点:5秒内调`startForeground`、耗时操作移出主线程、任务完成及时`stopSelf`。
499 0
|
存储 XML Java
Android 文件数据储存之内部储存 + 外部储存
简介:本文详细介绍了Android内部存储与外部存储的使用方法及核心原理。内部存储位于手机内存中,默认私有,适合存储SharedPreferences、SQLite数据库等重要数据,应用卸载后数据会被清除。外部存储包括公共文件和私有文件,支持SD卡或内部不可移除存储,需申请权限访问。文章通过代码示例展示了如何保存、读取、追加、删除文件以及将图片保存到系统相册的操作,帮助开发者理解存储机制并实现相关功能。
3284 2
|
23小时前
|
存储 JSON 缓存
快递物流查询接口说明:单号识别、物流轨迹与调用示例
快递物流查询接口用于根据快递单号获取包裹的物流轨迹与当前状态,支持国内外多家快递公司运单查询,可在不指定承运商时按单号自动识别快递公司。接口以 POST/GET 表单方式调用,通过 Authorization: APPCODE 鉴权,返回 JSON 结构,包含承运商信息、轨迹节点、状态码与更新时间。本文介绍请求参数、四种语言调用示例、返回字段、状态与返回码映射、调用规范及常见问题排查,适用于电商订单追踪、ERP/WMS 仓储同步与小程序物流展示等场景。
28 0

热门文章

最新文章