一、依赖注入基础概念与组件化项目中的依赖管理挑战
说实话,很多同学刚接触依赖注入时,都会被「控制反转」「DI容器」这些术语绕晕。
我个人习惯用一个生活例子来理解:你开了一家咖啡店。依赖就是「制作咖啡需要咖啡豆和牛奶」——如果你每天自己跑去进货、自己烘焙、自己挤牛奶,那叫「主动依赖」,耦合度极高。依赖注入就是让外卖小哥把原料送到店里,你只管做咖啡。
👉 核心思想:依赖的创建与获取,从类内部转移到外部。
1.1 到底什么是依赖注入?
依赖注入(Dependency Injection,简称DI)其实就三个字:给我用。
传统的写法,你在一个类里 new 另一个对象:
public class CoffeeMaker {
private Grinder grinder = new Grinder(); // 硬编码依赖
private WaterPump pump = new WaterPump();
public Coffee make() {
// ...
}
}
问题在哪?Grinder 和 WaterPump 被写死了。想换一个高级研磨机?得改代码。
依赖注入的写法:
public class CoffeeMaker {
private final Grinder grinder;
private final WaterPump pump;
// 依赖通过构造函数注入
public CoffeeMaker(Grinder grinder, WaterPump pump) {
this.grinder = grinder;
this.pump = pump;
}
}
嗯,其实就是把对象的创建权交出去。谁交?通常交给一个「DI容器」来统一管理。
注入的三种常见方式:
- 构造函数注入 —— 我最推荐,依赖明确,不可变,便于测试
- 属性/字段注入 —— 用 @Inject 直接标记字段,但依赖不透明,容易出空指针
- 方法/Setter注入 —— 灵活性高,但可能忘记调用
我在项目中遇到过一个坑:有同事全用字段注入,结果单元测试时 Mock 没注入进去,跑起来直接 NullPointerException。从那以后,我要求团队优先用构造函数注入。
1.2 为什么组件化项目需要DI?
你想想看,一个组件化项目,模块通常有几十个甚至上百个。比如电商App里,有 :user、:order、:payment、:product 等组件。
没有DI时,组件间互相 new 依赖,就会变成一团乱麻:
- 循环依赖:A组件 new 了B,B又 new 了A,编译报错
- 重复创建:每个页面都 new 一个网络请求实例,资源浪费
- 难以替换:想给测试环境注入Mock数据?得改多处代码
依赖注入帮你解决了这些问题。每个组件只声明「我需要什么」,不关心「谁给我」。DI容器负责把依赖组装好,在合适的时机注入。
1.3 组件化中的依赖管理挑战
讲几个我实际遇到的痛点,大家感受一下:
| 挑战 | 场景描述 | 后果 |
|---|---|---|
| 版本冲突 | 两个组件依赖同一个库的不同版本 | 编译失败或运行时 ClassNotFoundException |
| 作用域混乱 | LoginActivity 里的 UserManager 是单例,但另一个组件也创建了新的实例 | 数据不一致,用户信息乱窜 |
| 组件间通信耦合 | 组件A直接调用组件B的静态方法获取数据 | 拆不掉、改不动,违背组件化初衷 |
| 生命周期错乱 | Fragment 销毁后,注入的对象还在持有 Activity 引用 | 内存泄漏或崩溃:Attempt to invoke virtual method on a null reference |
当年我参与过一个项目,购物车组件和支付组件各自引入了不同版本的 OkHttp,编译时没报错,运行到某个页面直接因为 Gson 解析不兼容挂掉。排查了整整一个下午……后来我们用统一依赖版本管理 + 依赖注入来控制。
⚠️ 避坑指南: 曾经有个团队,在组件化初期图省事,让每个组件自己维护一个全局的 ServiceLocator。结果上线后出现大量「xxx is not bound」的运行时异常。因为组件 A 初始化顺序比组件 B 晚,B 调用 A 提供的服务时空指针了。
解决方案:要么用框架管理初始化顺序(如 Dagger 的 @Module + @Provides),要么用懒加载 + 条件检查。
1.4 本章知识体系概览
下面这张图,帮你把依赖注入的核心概念和组件化中的挑战串起来:
学习路径小贴士:
- 先搞懂 DI 的三个基本形式(构造/字段/方法),理解「谁控制谁」
- 再了解组件化项目中依赖管理的典型难题(版本、作用域、生命周期)
- 后续章节我们会逐一复现崩溃现场,手把手带你排查、修复
1.5 一个简单的Dagger示例
我们团队在组件化项目中常用 Dagger 或 Hilt。这里给个最简例子感受下:
// 定义依赖
public class UserRepository {
// ...
}
// 在组件中声明
@Module
public class AppModule {
@Provides
@Singleton
public UserRepository provideUserRepository() {
return new UserRepository();
}
}
// 注入到Activity
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
@Inject
UserRepository repository; // 别担心,Hilt会在构造时注入
}
看到没?你只管写 @Inject,框架帮你 new 好对象,且默认单例。如果多个组件需要同一个 UserRepository,不会各自创建——这避免了数据不一致的问题。
⚠️ 特别提醒: 不要在组件间共享不设防的单例。我曾经见过一个项目,把 Retrofit 实例做成全局单例,但每个组件往里面加了不同的拦截器……最后请求头混乱,排错排到崩溃。依赖注入不是万能药,它解决的是「谁来创建」的问题,而「如何设计依赖」还是得你自己负责。
小结
这一章我们聊了:
- 依赖注入的本质——把对象的创建和绑定从类内部移到外部
- 组件化项目中常见的四大依赖管理挑战:版本冲突、作用域混乱、通信耦合、生命周期错乱
- 梳理了课程的知识体系