Dagger 2 是一个基于 编译时依赖注入 的框架,其核心原理可以概括为以下几个关键点:
原理
1. 核心目标:解耦与自动化依赖管理
-
依赖注入 (DI):将对象的创建与其使用分离。类声明它需要什么依赖(如通过构造函数参数、字段标记),外部框架负责提供这些依赖实例。
-
解耦:使用者不关心依赖如何创建,只关心接口/类型。
-
可测试性:依赖可轻松替换为模拟对象。
2. 核心机制:编译时注解处理
-
APT (Annotation Processing Tool):
-
Dagger 2 在编译时扫描源代码中的注解(如
@Inject
,@Module
,@Provides
,@Component
)。 -
生成可读的 Java 源码(在
build/generated/source/apt
目录下),而非使用反射(这是运行时 DI 框架的特点)。
-
-
优势:
-
高性能:运行时无反射开销。
-
编译时错误检查:依赖关系不满足(如缺少绑定、循环依赖)会在编译时报错,而非运行时崩溃。
-
可追溯性:生成的代码可阅读,便于调试。
-
3. 核心组件及其协作
-
@Inject
:-
标记依赖需求:在构造函数、字段或方法上使用,表示“我需要这个依赖”。
-
标记依赖提供者:在类的构造函数上使用,表示“我可以提供自身实例”。
-
-
@Module
与@Provides
:-
@Module
:标记类,作为依赖提供的“模块”。包含无法用@Inject
构造的依赖(如接口、第三方库对象)。 -
@Provides
:标记模块中的方法,该方法返回一个依赖实例(如provideHttpClient()
)。
-
-
@Component
:-
核心桥梁:标记接口/抽象类,连接 Modules(提供依赖)和 注入目标(使用依赖)。
-
职责:
-
组合相关 Module(s)。
-
定义注入目标(如
inject(MainActivity activity)
)。 -
暴露特定依赖给其他 Component(通过接口方法)。
-
-
Dagger 生成实现类:如
DaggerAppComponent
,包含完整的依赖图解析逻辑。
-
-
@Singleton
/ Scope 注解:-
控制生命周期:标记依赖在指定范围内(如全局 App、Activity)保持单例。
-
实现原理:通过 Component 内部缓存实例(如
DoubleCheck
包装的 Provider),而非传统全局静态变量。
-
4. 依赖解析流程(以注入 Activity 为例)
-
声明注入点:在 Activity 中标记
@Inject
字段。 -
创建 Component:构建 Dagger 生成的 Component 实现(如
DaggerAppComponent.create()
)。 -
触发注入:调用
component.inject(this)
。 -
依赖图解析:
-
Component 查找 Activity 所需依赖的类型。
-
按优先级查找绑定:
-
检查 Module 中的
@Provides
方法。 -
检查
@Inject
构造函数的类。 -
检查父 Component 暴露的依赖(若涉及层级)。
-
-
自动递归:解析依赖的依赖,直至所有叶子节点(无依赖的基础类型或已绑定类型)。
-
-
注入实例:将解析好的依赖实例赋值到 Activity 的
@Inject
字段。
5. 关键高级特性原理
-
Subcomponents vs. Component Dependencies:
-
Subcomponent:继承父 Component 的所有绑定,可访问父的依赖。生命周期更短(如
@ActivityScope
)。通过父 Component 的工厂方法创建。 -
Component Dependencies:通过接口显式暴露依赖,松耦合但需手动声明。
-
-
Qualifiers (
@Named
,@Qualifier
):区分相同类型的不同实例(如@Named("apiUrl") String url
)。 -
Multibindings:将多个绑定聚合到一个集合(如
Map<Class, Provider>
),用于插件式架构。 -
Lazy / Provider:
-
Lazy<T>
:延迟初始化,首次get()
时创建。 -
Provider<T>
:每次get()
返回新实例或重新获取。
-
使用
一、复杂依赖管理场景
当对象依赖层级深、创建逻辑复杂时,手动管理依赖会导致代码冗余且易错。Dagger2 通过自动生成依赖注入代码,显著简化此类场景:
-
多层级依赖
例如:CoffeeMachine
依赖CoffeeMaker
,CoffeeMaker
又依赖Cooker
。
传统方式需嵌套new
调用:
new CoffeeMachine(new CoffeeMaker(new Cooker()))
Dagger2 通过@Inject
构造函数 +@Component
自动解析依赖链,无需显式创建对象36。 -
第三方库或接口注入
无法修改源码的类(如 Retrofit、Room 等),可通过@Module
+@Provides
提供实例:@Module public class ApiModule { @Provides Retrofit provideRetrofit() { return new Retrofit.Builder().baseUrl(BASE_URL).build(); } }
二、模块化开发场景
大型项目需按功能拆分为独立模块,Dagger2 支持组件化依赖管理:
-
子组件(Subcomponent)
实现作用域隔离(如全局AppComponent
→ 页面级ActivityComponent
),子组件继承父组件的依赖,避免重复创建单例。 -
组件依赖(Component Dependencies)
松耦合的模块间通信,例如UserComponent
依赖AppComponent
获取全局数据库实例。
三、测试优化场景
依赖注入天然支持替换实现,提升单元测试效率:
-
Mock 依赖注入
测试时替换@Module
的实现,例如将网络模块替换为本地模拟数据源:@Module public class TestNetworkModule { @Provides ApiService provideFakeApiService() { return new MockApiService(); // 模拟网络请求 } }
-
环境差异化配置
Debug 与 Release 环境使用不同 Module(如日志开关、API 地址)。
四、Android 特定场景
Dagger2 在 Android 开发中尤其高效,解决平台特有痛点:
-
生命周期绑定
通过dagger.android
库自动注入Activity
/Fragment
,避免手动调用component.inject(this)
。 -
作用域管理
使用@Singleton
(应用级)、@PerActivity
(页面级)等自定义作用域,控制实例生命周期(如单例在应用退出时才销毁)。 -
资源解耦
Context
、SharedPreferences
等系统服务通过 Module 统一提供,避免在业务类中硬编码。
五、性能敏感场景
相比反射型框架(如 Guice),Dagger2 的编译时代码生成带来显著性能优势:
-
无运行时反射开销:生成的代码效率与手写相当26。
-
编译时验证:依赖缺失或循环依赖在编译期报错,避免运行时崩溃48。
六、作用域管理场景
通过作用域注解控制对象复用策略,适应不同需求:
场景 | 实现方式 | 示例 |
---|---|---|
全局单例 | @Singleton | 数据库实例、全局配置 |
延迟初始化 | Lazy<T> | 耗时对象首次访问时才创建 |
每次获取新实例 | Provider<T> | 每次请求生成新对象 |
页面级单例 | @PerActivity | Presenter 绑定 Activity 生命周期 |
总结:Dagger2 适用场景对比
场景类型 | 核心需求 | Dagger2 方案 | 优势 |
---|---|---|---|
多层级依赖 | 简化对象创建流程 | @Inject + @Component | 消除嵌套 new ,自动解析依赖链 |
模块化开发 | 组件间解耦 | Subcomponent/依赖组件 | 作用域隔离,依赖复用 |
单元测试 | 快速替换依赖实现 | 测试专用 Module | 无需修改生产代码 |
Android 平台 | 生命周期绑定 | dagger.android 扩展库 | 减少模板代码 |
高性能要求 | 避免运行时开销 | 编译时代码生成 | 接近手写性能 |
💡 实际项目建议:
-
中小项目:从关键页面(如登录模块)逐步引入,避免过度设计。
-
大型应用:结合 Clean Architecture 或 MVP/MVVM,使用 Dagger2 管理全局
Repository
、DataSource
等。 -
替代方案考量:若项目依赖简单,可评估是否需引入 Dagger2,避免增加学习成本。