首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Dagger2 依赖注入深度解析:编译时注解处理与依赖图生成

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

原创
作者头像
hunter android
发布于 2026-10-27 10:30:20
发布于 2026-10-27 10:30:20
1130
举报

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

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


为什么需要依赖注入

传统依赖管理的问题

代码语言:javascript
复制
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 的解决方案

代码语言:javascript
复制
@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 在编译期扫描注解,生成工厂类和注入器:

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

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

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

代码语言:javascript
复制
// 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),检查循环依赖和缺失依赖:

代码语言:javascript
复制
@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
)

依赖关系图:

代码语言:javascript
复制
AppComponent
  └─ UserRepository
       ├─ UserDao
       │   └─ AppDatabase (from DataModule)
       └─ ApiService (from NetworkModule)

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


3. Scope 作用域管理

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

代码语言:javascript
复制
@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 级单例)

实现原理:

代码语言:javascript
复制
// 生成的单例缓存
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 模块

代码语言:javascript
复制
// :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
代码语言:javascript
复制
class MyApplication : Application() {
    lateinit var appComponent: AppComponent

    override fun onCreate() {
        super.onCreate()
        appComponent = DaggerAppComponent.builder()
            .appModule(AppModule(this))
            .build()
    }
}
  1. Activity 创建 UserComponent
代码语言:javascript
复制
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 注入延迟初始化

代码语言:javascript
复制
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 多次获取新实例

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

3. Multibindings 集合注入

代码语言:javascript
复制
@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

代码语言:javascript
复制
error: [Dagger/MissingBinding] ApiService cannot be provided

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

解决:

代码语言:javascript
复制
@Module
class NetworkModule {
    @Provides
    fun provideApiService(retrofit: Retrofit): ApiService =
        retrofit.create(ApiService::class.java)
}

2. 循环依赖

代码语言:javascript
复制
error: [Dagger/DependencyCycle] Found a dependency cycle:
  A -> B -> C -> A

解决方案: - 使用 Lazy<T> 打破循环 - 重新设计依赖关系

3. Scope 不匹配

代码语言:javascript
复制
@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 架构设计的重要一环。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • Dagger2 依赖注入深度解析:编译时注解处理与依赖图生成
    • 为什么需要依赖注入
      • 传统依赖管理的问题
      • Dagger2 的解决方案
    • Dagger2 核心原理
      • 1. APT 注解处理器
      • 2. 依赖图生成
      • 3. Scope 作用域管理
    • 实战:多模块依赖注入
      • 场景:App + Feature 模块
      • 注入流程
    • 进阶技巧
      • 1. Lazy 注入延迟初始化
      • 2. Provider 多次获取新实例
      • 3. Multibindings 集合注入
    • 与 Hilt 的对比
    • 常见问题排查
      • 1. 编译报错:Missing binding
      • 2. 循环依赖
      • 3. Scope 不匹配
    • 总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档