依赖注入通过外部管理依赖创建,使对象专注业务逻辑、解耦实现并提升可测性;要求面向接口编程,支持构造函数、setter和字段等多种注入方式,其中构造函数注入最利于测试与健壮性。

依赖注入(DI)是实现控制反转(IoC)最常用、最直接的方式。它把“谁创建依赖”这件事从类内部拿走,交给外部统一管理,从而让对象只专注自己的业务逻辑,不操心依赖从哪来、怎么初始化。这种转变自然带来两个关键好处:对象之间不再硬绑定具体实现,测试时也容易替换依赖为模拟对象。
依赖关系不再由类自己创建
传统写法中,一个服务类常通过 new 直接实例化它的依赖,比如:
UserService userSvc = new UserService(new UserRepositoryImpl(), new LoggerImpl());
这导致 UserService 和 UserRepositoryImpl、LoggerImpl 紧密耦合,一旦要换数据库实现或改日志方式,就得改 UserService 的代码。而用 DI 后,UserService 只声明需要什么,不负责创建:
- 构造函数里只接收接口类型参数,如 UserRepository 和 Logger
- 具体实现由外部容器或调用方在创建 UserService 时传入
- UserService 内部不再出现 new 任何依赖类
面向接口编程,天然支持替换
DI 要发挥作用,前提是依赖声明为抽象(接口或抽象类),而不是具体类。例如:
- 定义 UserRepository 接口,含 findById()、save() 等方法
- 提供多个实现:JdbcUserRepository(连真实数据库)、InMemoryUserRepository(内存模拟)、StubUserRepository(返回固定数据)
- UserService 构造函数只接受 UserRepository 类型,不关心具体是哪个实现
这样,同一份 UserService 代码,在生产环境用 Jdbc 实现,在集成测试用 InMemory,在单元测试用 Stub —— 完全无需修改业务类本身。
测试时轻松注入模拟依赖
单元测试的核心是隔离。DI 让你能在测试中精准控制依赖行为:
- 写测试时,手动 new 一个 MockUserRepository,预设好 findById(123) 返回特定用户对象
- 把这个 mock 对象传给 UserService 构造函数,再调用 getUserProfile(123)
- 断言结果是否符合预期,完全绕过数据库、网络等外部因素
- Spring Test、pytest-mock、Jest 等框架还能自动帮你生成和注入 mock 实例
注入方式影响可测性与健壮性
不同注入方式对可测试性和设计质量有实际影响:
- 构造函数注入:依赖在对象创建时就确定,不可变、必填,适合核心依赖;测试时必须传齐所有依赖,强迫你思考完整场景
- Setter 注入:依赖可选、可后期替换,适合配置类或策略类;测试中可只设置关心的依赖,忽略其他
- 字段注入(如 @Autowired):写法简洁但隐藏依赖,不利于单元测试(无法在 new 对象时传入 mock),也不便于脱离容器做纯逻辑验证










