IoC 与 DI:控制反转思想与容器职责
引言
没有容器时,new 一个依赖对象、并在对象内部到处 new 别的对象,会让代码层层耦合、极难替换与测试。IoC(控制反转)把“创建谁、把谁注入给谁”的决定权从业务代码交给容器;DI(依赖注入)正是实现这种反转的具体手段。本章讲清思想与容器的职责边界。
核心概念
- IoC:传统写法中对象自己掌控依赖的创建(“正向”);反转后,对象只声明“我需要什么”,由外部容器在合适的时机注入——对象从“主动找”变成“被动收”。
- DI:容器注入依赖的三种常见方式:构造器注入、setter 注入、字段注入(第 04 章展开)。
- 容器职责:负责 Bean 的实例化、装配(解析并注入依赖)、管理生命周期(初始化/销毁回调)、提供查找入口(
getBean),并集成 AOP、事件、资源加载等能力。 - BeanFactory 与 ApplicationContext:
BeanFactory是最底层的 IoC 容器接口(getBean等);ApplicationContext继承并扩展了它,额外提供事件发布、国际化(MessageSource)、资源加载(ResourceLoader)、环境抽象(Environment)等能力,日常都使用 ApplicationContext 及其子类。 - 常用容器实现:
AnnotationConfigApplicationContext(注解/JavaConfig)、ClassPathXmlApplicationContext(XML)、AnnotationConfigWebApplicationContext(Web)。
代码示例
// 1) 耦合版:服务自己 new 依赖,难以替换与测试
public class OrderService {
private final SmsSender sender = new AliSmsSender(); // 写死实现
}
// 2) 反转版:只依赖接口,实现由容器注入
public interface SmsSender { void send(String to, String text); }
@Component
public class OrderService {
private final SmsSender sender; // 只声明需求
public OrderService(SmsSender sender) { this.sender = sender; }
}
@Component
public class AliSmsSender implements SmsSender {
public void send(String to, String text) { /* 发短信 */ }
}
// 3) 启动容器,让容器完成"装配"
public class Main {
public static void main(String[] args) {
try (AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext("com.example")) {
OrderService service = ctx.getBean(OrderService.class); // 已带好 sender
// 想换成其他实现?换一个 @Component 实现类即可,业务代码零改动
}
}
}
注意点
- 依赖请面向接口/抽象声明,容器负责绑定具体实现,替换成本才低。
- 容器默认按类型匹配 Bean;多个同类型候选时按名称等规则裁决(第 04 章)。
- 只实现了
BeanFactory语义的裸容器能力有限,除非做框架级定制,否则一律面向ApplicationContext编程。 - 容器关闭时才会执行销毁回调,别忘了
close()或registerShutdownHook()。
小结
IoC 是一种设计思想:把装配控制权反转给容器;DI 是它的落地方式。BeanFactory/ApplicationContext 承担了实例化、注入、生命周期与查找的职责。写业务时你只需声明依赖、标注组件,剩下的交给容器——这正是后续所有章节的地基。