谈谈 Spring 的 IOC?
结论先行:IoC(Inversion of Control,控制反转)是一种设计思想——对象的创建与依赖装配的控制权,从业务代码"反转"给容器;DI(Dependency Injection,依赖注入)是它的主要实现方式。Spring 的 IoC 容器读取配置(注解/XML/Java Config)得到 BeanDefinition,再按需实例化、装配、缓存、销毁。收益是解耦、可替换、可测试、生命周期统一管理;代价是"对象从哪来"变得隐式,排查问题需要理解容器。
一、反转了什么
传统写法(控制权在业务代码):
OrderService ──new──▶ UserDaoImpl ──new──▶ DataSource
依赖写死、无法替换、单测要连库
IoC 写法(控制权在容器):
容器 ──创建──▶ DataSource ──注入──▶ UserDaoImpl ──注入──▶ OrderService
业务代码只声明"我需要什么",不关心"谁给我、怎么造"
二、容器家族
| 类型 | 特点 | 何时用 |
|---|---|---|
BeanFactory | 最底层容器,懒加载,只提供 getBean 等基础能力 | 资源极度受限、按需取 Bean |
ApplicationContext | 继承 BeanFactory,启动时预实例化单例,增加事件、国际化、资源加载、AOP 集成 | 绝大多数应用 |
WebApplicationContext | 增加 Servlet 上下文与 Web 作用域 | Web 应用 |
AnnotationConfigApplicationContext | 基于注解配置类 | 纯注解项目、单测 |
GenericApplicationContext | 可编程式注册 BeanDefinition | 框架集成、动态注册 |
三、依赖注入的三种方式
| 方式 | 写法 | 优点 | 缺点 |
|---|---|---|---|
| 构造器注入 | 构造器参数 + final 字段 | 依赖不可变、必填、循环依赖即报错、易单测 | 参数多时构造器臃肿 |
| Setter 注入 | @Autowired 标在 setter | 可选择性注入、可重配置 | 对象可能处于半初始化状态 |
| 字段注入 | @Autowired 标在字段 | 代码最短 | 无法 final、难单测、隐藏依赖、易滥用 |
四、代码:同一份依赖的三种写法
@Service
public class OrderService {
// 推荐:构造器注入(Spring 4.3+ 单构造器可省略 @Autowired)
private final UserDao userDao;
private final PayClient payClient;
public OrderService(UserDao userDao, PayClient payClient) {
this.userDao = userDao;
this.payClient = payClient;
}
}
@Configuration
@ComponentScan("com.demo")
public class AppConfig {
@Bean
public PayClient payClient(@Value("${pay.endpoint}") String endpoint) {
return new PayClient(endpoint);
}
public static void main(String[] args) {
try (AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(AppConfig.class)) {
OrderService svc = ctx.getBean(OrderService.class);
// 容器负责关闭时调用销毁回调
}
}
}
五、容器启动时做了什么
AbstractApplicationContext#refresh() 是 IoC 的主线,可概括为:
prepareRefresh 准备上下文、校验必需属性
obtainFreshBeanFactory 创建 DefaultListableBeanFactory
prepareBeanFactory 注册内建依赖(环境、资源解析器、事件发布器)
invokeBeanFactoryPostProcessors 解析配置类、执行 @Configuration 增强、自动配置
registerBeanPostProcessors 注册 BPP(AOP、@Autowired 的处理者)
initMessageSource / initApplicationEventMulticaster
onRefresh 子类扩展:Web 容器在此启动
registerListeners
finishBeanFactoryInitialization 实例化所有非懒加载单例(生命周期主线)
finishRefresh 发布 ContextRefreshedEvent、启动 Lifecycle 组件
六、常见追问
- 问:IoC 和 DI 是一回事吗? 不是。IoC 是思想(控制权反转),DI 是实现手段;还有依赖查找(Dependency Lookup,主动
getBean)也是 IoC 的一种实现。 - 问:BeanDefinition 是什么? Bean 的"图纸":类名、作用域、是否懒加载、构造参数、属性值、初始化方法等,容器据此生产实例。
- 问:为什么 ApplicationContext 启动时就把单例都创建好? 为了尽早暴露配置错误(缺依赖、循环依赖、切点错误),代价是启动变慢;可用
spring.main.lazy-initialization=true改为懒加载。 - 问:依赖注入能注入基本类型吗? 用
@Value注入配置值,或@ConfigurationProperties批量绑定,本质是类型转换后的属性填充。
小结
讲 IoC 抓住三句话:控制权反转给容器、DI 是主要实现、BeanDefinition 是中间产物。再补"容器有 BeanFactory 与 ApplicationContext 两档、注入优先构造器、refresh 是启动主线",就是一条完整的答题链。