第二讲:Dagger/Hilt 核心注解 —— 依赖注入的“四根柱子”
大家好,我是你们的老朋友。
上一章我们聊了组件化架构下依赖管理有多头疼。今天,咱们直接切入正题,来解剖一下 Dagger 和 Hilt 里最核心的四个注解:@Inject、@Module、@Provides、@Component。
很多新手一上来就被这些注解砸晕了。别慌。说白了,它们就是依赖注入的“四根柱子”。你把这四根柱子的职责搞清楚了,后面写代码会顺手很多。
我个人习惯把这四个注解分成两组看:
- 第一组:提供依赖的(@Inject 和 @Provides)
- 第二组:管理依赖的(@Module 和 @Component)
这样是不是清晰多了?
1. 先说 @Inject:最基础的依赖生产者
@Inject 有两个作用,你千万别搞混了。
- 作用一:标注构造方法 —— 告诉 Dagger:“嘿,这个类我可以自己生出来,你拿着我的构造方法用就行。”
- 作用二:标注成员变量 —— 这是消费端。告诉 Dagger:“我这里需要一个对象,你帮我注入进来。”
举个例子,我当初一个项目里有个网络请求库,直接就在构造方法上加了 @Inject:
public class ApiService {
@Inject
public ApiService(OkHttpClient client, String baseUrl) {
// 这里用了构造方法注入
}
}
你看,这就是典型的 @Inject 提供依赖。但这里有个坑 —— 如果构造方法参数里,比如 OkHttpClient 你没告诉 Dagger 怎么创建它,那 @Inject 自己就玩不转了。这时候就得请出下面两位。
2. @Module 与 @Provides:专门伺候第三方库和配置
有些东西不是你写的类,比如 Retrofit、OkHttp,甚至是 String、Integer 这样的基础类型。你总不能给 String 的构造方法加个 @Inject 吧?
所以,我们才需要 @Module + @Provides。
@Module: 就是一个“工具箱”的标签。你把所有创建依赖的方法都放到这个“工具箱”里。@Provides: 就是工具箱里的具体工具。标注在方法上,告诉 Dagger:“这个方法你给我保存好,将来谁需要这个对象,你就调这个方法。”
我写过这样一个 Module:
@Module
public class NetworkModule {
@Provides
public OkHttpClient provideOkHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build(); // 我亲自创建实例
}
@Provides
public Retrofit provideRetrofit(OkHttpClient client) {
return new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(client)
.build();
}
}
你想想看,Retrofit 依赖 OkHttpClient。在 provideRetrofit 方法里,我直接把 OkHttpClient 参数写上就行了。Dagger 会自动找到 provideOkHttpClient 来提供这个参数。
@Module 里,方便后期替换或者单元测试。你永远不知道什么时候会换一个网络库。
3. @Component:最大的“管家”
前三个注解只是“零件”,真正把它们组装起来的是 @Component。它就是一个 依赖图 的入口。
你想想看,如果 @Inject 和 @Module 都各自运行,谁管哪儿去找谁?@Component 就干这个活。
- 它把
@Inject标记的构造方法、@Module里的@Provides方法统一管理起来。 - 它定义了一个“容器”,包含所有依赖的创建规则。
- 它负责把这些依赖注入到目标类(比如 Activity、Fragment)里。
我们来看个最简单的例子:
@Component(modules = NetworkModule.class)
public interface AppComponent {
void inject(MainActivity activity);
}
嗯,这代码看起来很简单吧?但它背后干的事可不少。@Component 在编译时会自动生成代码,帮你实例化 NetworkModule 里所有 @Provides 方法返回的对象,然后再注入到 MainActivity 里。
@Component,并且都依赖同一个 Module。结果运行时 Dagger 报了一个“重复绑定”的错误。后来我发现,@Component 是有作用域的。不同的 @Component 如果不指定 @Singleton,它会生成不同的实例。所以记得用 @Singleton 或者自定义 Scope 来控制生命周期。
4. 一张图理清这四者的关系
为了让大家更直观地理解,我画了一张架构图。你看看这四个注解是怎么协同工作的。
5. 总结一下这四个注解的职责
我直接给你整理成一张对比表,方便你平时查阅:
| 注解 | 职责 | 适用场景 | 个人经验 |
|---|---|---|---|
| @Inject | 标记构造方法 或成员变量 |
自己写的类,构造方法不需要额外配置 | 最偷懒的方式,大部分业务逻辑类我都这么写 |
| @Module | 声明一个“提供依赖”的容器 | 第三方库、配置类、抽象类的提供 | 我习惯按业务模块拆成多个小 Module |
| @Provides | 具体的依赖创建方法 | 放在 @Module 类中,返回要注入的对象 | 千万别让它做耗时操作,否则 ANR 等着你 |
| @Component | 依赖图的入口和桥梁 | 连接提供端和消费端,定义全局注入规则 | 组件化里每个 Feature 可以有自己的 Component |
@Inject 提供能力,@Module/@Provides 补充能力,@Component 调度能力。少了任何一个,依赖注入的闭环就断了。