以下是为你精心撰写的 《2.1 Spring 核心容器:IoC 与 DI 的实现原理》 深度说明文档,系统性地剖析 Spring 如何通过 反射、注解解析、依赖注入,将一堆普通的 Java 对象(POJO)组装成一个可运行、可管理、可扩展的企业级应用系统。
本章内容是理解 Spring 的基石,也是面试高频考点。你将不仅“会用”,更能“知其所以然”。
📜 2.1 Spring 核心容器:IoC 与 DI 的实现原理
目标:深入理解 Spring 容器如何通过反射、注解解析与依赖注入,将 POJO 组装成可运行系统
✅ 一、IoC 容器:Spring 的心脏 —— BeanFactory 与 ApplicationContext
1.1 什么是 IoC 容器?
IoC(Inversion of Control,控制反转)是一种设计思想,其核心是:对象的创建与依赖关系的管理,不再由程序代码主动控制,而是交给外部容器来完成。
在 Spring 中,这个“外部容器”就是 IoC 容器。它负责:
- 读取配置(XML / 注解 / Java 配置)
- 实例化 Bean
- 注入依赖
- 管理生命周期
- 提供 AOP、事务、事件等扩展能力
1.2 BeanFactory vs ApplicationContext:谁才是真正的“主角”?
| 特性 | BeanFactory | ApplicationContext |
|---|---|---|
| 定位 | 最基础的 IoC 容器 | 企业级容器,扩展自 BeanFactory |
| 加载时机 | 懒加载(第一次 getBean 时才初始化) | 预加载(启动时初始化所有 singleton Bean) |
| 功能 | 仅提供基本的 getBean、依赖注入 | 支持:AOP、事件发布、国际化、资源加载、Web 环境集成 |
| 性能 | 轻量,内存占用少 | 较重,但功能完整 |
| 使用场景 | 嵌入式系统、资源受限环境 | 绝大多数企业项目(推荐) |
| 典型实现 | DefaultListableBeanFactory | ClassPathXmlApplicationContext, AnnotationConfigApplicationContext |
✅ 结论:
ApplicationContext是BeanFactory的超集,是 Spring 开发的默认选择。
除非你明确需要极致轻量(如 Android、嵌入式设备),否则永远使用ApplicationContext。
1.3 源码视角:容器如何启动?
// 1. 使用注解配置启动容器
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 2. 底层调用链(简化):
// AnnotationConfigApplicationContext → refresh() →
// → prepareRefresh() → obtainFreshBeanFactory() →
// → loadBeanDefinitions() → scanPackages() →
// → registerBeanDefinitions() →
// → doCreateBean() → createBeanInstance() → populateBean() → initializeBean()
💡 关键点:
ApplicationContext的refresh()方法是整个容器启动的“总开关”,它触发了从配置扫描 → Bean 定义注册 → 实例化 → 初始化的完整流程。
✅ 二、Bean 的生命周期:从“无”到“有”,再到“灭”
Spring 容器管理的每个 Bean 都经历一个清晰的生命周期。理解它,是掌握自动装配、AOP、自定义扩展的前提。
2.1 Bean 生命周期七阶段(按顺序)
| 阶段 | 说明 | 关键接口/注解 |
|---|---|---|
| 1. 实例化(Instantiation) | 容器通过反射调用构造器创建 Bean 实例 | Constructor |
| 2. 属性赋值(Populate Properties) | 容器根据 @Autowired、@Value、XML 配置注入依赖 | @Autowired, @Value, setXxx() |
| 3. 设置 Bean 名称(Set Bean Name) | 若 Bean 实现 BeanNameAware,则注入其名称 | setBeanName(String name) |
| 4. 设置 Bean 工厂(Set Bean Factory) | 若实现 BeanFactoryAware,注入容器本身 | setBeanFactory(BeanFactory bf) |
| 5. 初始化前(Post-Process Before Initialization) | 执行 BeanPostProcessor.postProcessBeforeInitialization() | BeanPostProcessor |
| 6. 初始化(Initialization) | 执行初始化方法 | @PostConstruct, InitializingBean.afterPropertiesSet(), init-method |
| 7. 初始化后(Post-Process After Initialization) | 执行 BeanPostProcessor.postProcessAfterInitialization() | BeanPostProcessor |
| 8. 使用中(Ready) | Bean 可被其他 Bean 或业务代码调用 | — |
| 9. 销毁前(Pre-Destruction) | 执行销毁前回调 | @PreDestroy, DisposableBean.destroy(), destroy-method |
| 10. 销毁(Destruction) | 容器关闭时释放资源 | — |
✅ 生命周期图示(推荐记忆顺序):
实例化 → 属性注入 → 设置名称/工厂 → 初始化前 → 初始化 → 初始化后 → 使用 → 销毁前 → 销毁
2.2 初始化与销毁的三种方式对比
| 方式 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
@PostConstruct / @PreDestroy | JSR-250 标准,语义清晰,不依赖 Spring API | 需要引入 javax.annotation(Java 11+ 需额外依赖) | ⭐⭐⭐⭐⭐(首选) |
InitializingBean / DisposableBean | Spring 原生,无需注解 | 侵入性强,耦合 Spring API | ⭐⭐(不推荐) |
XML 配置 init-method / destroy-method | 灵活,适用于非注解项目 | 配置分散,维护困难 | ⭐⭐⭐(XML 项目可用) |
✅ 示例:三种方式实现同一个初始化逻辑
@Component
public class UserService implements InitializingBean, DisposableBean {
@PostConstruct
public void initByAnnotation() {
System.out.println("✅ @PostConstruct 初始化");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("✅ InitializingBean 初始化");
}
public void customInit() {
System.out.println("✅ init-method 初始化");
}
@PreDestroy
public void destroyByAnnotation() {
System.out.println("✅ @PreDestroy 销毁");
}
@Override
public void destroy() throws Exception {
System.out.println("✅ DisposableBean 销毁");
}
public void customDestroy() {
System.out.println("✅ destroy-method 销毁");
}
}
⚠️ 注意:
@PostConstruct和InitializingBean.afterPropertiesSet()会同时执行,顺序不确定,避免重复逻辑。
✅ 三、依赖注入(DI)的三种方式:为什么推荐构造器注入?
3.1 什么是依赖注入(DI)?
DI 是 IoC 的具体实现方式:容器将 Bean 所依赖的其他对象“注入”到它内部,而不是由 Bean 自己创建或查找。
3.2 三种注入方式对比
| 方式 | 示例 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|---|
| 构造器注入(Constructor Injection) | @Autowired<br>public UserService(UserRepository repo) { this.repo = repo; } | - 依赖不可变(final) - 保证对象创建时依赖已就绪 - 易于单元测试(构造器传 Mock) - 无循环依赖风险(Spring 会报错) | 需要写构造器(IDE 可自动生成) | ⭐⭐⭐⭐⭐ 强烈推荐 |
| Setter 注入(Setter Injection) | @Autowired<br>public void setUserRepository(UserRepository repo) { this.repo = repo; } | - 支持可选依赖 - 支持循环依赖(Spring 可解决) | - 依赖可被外部修改 - 对象可能处于“不完整”状态 | ⭐⭐⭐(适用于可选依赖) |
| 字段注入(Field Injection) | @Autowired<br>private UserRepository userRepository; | - 最简洁,代码最少 | - 无法 final,对象不安全 - 无法单元测试(需反射) - 隐藏依赖,违反“显式依赖”原则 - IDE 无法提示依赖缺失 | ⭐(仅限快速原型,生产环境禁止) |
🔥 为什么 Spring 官方推荐构造器注入?
✅ 官方立场(Spring Docs):
“Constructor injection is preferred because it allows you to implement your components as immutable objects and ensures that required dependencies are not null.”
- 不可变性:
final字段 + 构造器注入 = 线程安全、状态稳定 - 强制依赖:没有依赖,Bean 就创建失败 → 开发阶段暴露问题
- 测试友好:
new UserService(mockRepo)即可测试,无需 Spring 容器 - 符合依赖倒置原则(DIP)
💡 反例警示:
@Component public class OrderService { @Autowired private PaymentService paymentService; // ❌ 字段注入,隐藏依赖 public void process() { paymentService.charge(); // 运行时可能 NPE! } }若
PaymentService未正确配置,编译通过,运行崩溃 —— 这是灾难性的。
✅ 四、Bean 作用域(Scope):控制对象的“生命周期与可见性”
Spring 支持多种作用域,决定一个 Bean 在容器中是“单例”还是“每次请求都新建”。
| 作用域 | 说明 | 适用场景 | 默认 |
|---|---|---|---|
singleton | 容器中只有一个实例,所有请求共享 | 服务类、工具类、DAO、Repository | ✅ 默认 |
prototype | 每次 getBean() 都新建一个实例 | 有状态的 Bean(如 Session、临时对象) | ❌ |
request | 每个 HTTP 请求创建一个实例 | Web 环境下,存储请求级数据(如用户信息) | ❌ |
session | 每个 HTTP Session 创建一个实例 | 用户登录状态、购物车 | ❌ |
application | 每个 ServletContext 一个实例(Servlet 容器级别) | 应用级缓存、全局配置 | ❌ |
websocket | 每个 WebSocket 连接创建一个实例 | 实时通信场景(如聊天室) | ❌ |
✅ 使用方式:
@Component
@Scope("prototype") // 每次获取都新建
public class Cart {
private List<Item> items = new ArrayList<>();
}
@Component
@Scope("request") // 每次 HTTP 请求新建
public class RequestContext {
private String userId;
}
⚠️ 注意:
request、session等作用域只能在 Web 环境中使用(如 Spring MVC、Spring Boot Web),否则启动报错。
🔍 深入:prototype Bean 与 singleton 的依赖关系
@Component
@Scope("singleton")
public class OrderService {
@Autowired
private Cart cart; // ❌ 问题:Cart 是 prototype,但被 singleton 引用
}
⚠️ 问题:
OrderService是单例,只在启动时注入一次Cart,后续所有请求都用同一个 Cart 实例,导致数据混乱!
✅ 正确做法:使用 ObjectFactory 或 @Lookup
@Component
@Scope("singleton")
public class OrderService {
@Autowired
private ObjectFactory<Cart> cartFactory; // 每次调用 get() 都新建
public void createOrder() {
Cart cart = cartFactory.getObject(); // ✅ 每次获取新实例
// ...
}
}
或使用 @Lookup(方法注入):
@Component
@Scope("singleton")
public class OrderService {
@Lookup
public Cart createCart() {
return null; // Spring 会动态生成实现
}
public void createOrder() {
Cart cart = createCart(); // ✅ 每次调用都返回新实例
}
}
✅ 五、Bean 的命名与别名:如何精确控制 Bean 的“身份”
5.1 Bean 的默认命名规则
-
若使用
@Component,默认名称是类名首字母小写:@Component public class UserService { ... } // → bean name: "userService" -
若使用
@Service、@Repository、@Controller,规则相同。
5.2 自定义 Bean 名称
@Component("myUserService") // 自定义名称
public class UserService { ... }
// 获取时:
UserService user = context.getBean("myUserService", UserService.class);
5.3 别名(Alias):一个 Bean 多个名字
@Component("userService")
@AliasFor("myUserService")
public class UserService { ... }
或通过 Java 配置:
@Configuration
public class AppConfig {
@Bean("userService")
@AliasFor("userSvc")
public UserService userService() {
return new UserService();
}
}
或 XML 配置:
<bean id="userService" class="com.example.UserService"/>
<alias name="userService" alias="userSvc"/>
5.4 @Qualifier:解决同类型 Bean 的歧义
问题:多个相同类型的 Bean,如何指定注入哪一个?
@Component
public class MySQLRepository implements UserRepository { ... }
@Component
public class RedisRepository implements UserRepository { ... }
@Component
public class UserService {
@Autowired
private UserRepository repository; // ❌ 报错:No unique bean of type 'UserRepository'
}
✅ 解决方案:@Qualifier
@Component("mysqlRepo")
public class MySQLRepository implements UserRepository { ... }
@Component("redisRepo")
public class RedisRepository implements UserRepository { ... }
@Component
public class UserService {
@Autowired
@Qualifier("mysqlRepo") // 明确指定
private UserRepository repository;
}
✅ 替代方案:使用
@Primary标记默认 Bean:
@Primary
@Component
public class MySQLRepository implements UserRepository { ... }
此时,@Autowired 会自动注入 MySQLRepository,无需 @Qualifier。
✅ 六、补充:IoC 容器的实现原理(源码级理解)
6.1 核心类图(简化)
BeanFactory
└── DefaultListableBeanFactory(核心实现)
├── registerBeanDefinition() // 注册 Bean 定义
├── getBean() // 获取 Bean(触发创建)
├── doCreateBean() // 创建 Bean 实例
│ ├── instantiateBean() // 反射调用构造器
│ ├── populateBean() // 属性注入(反射 + 注解解析)
│ └── initializeBean() // 初始化回调
└── getSingleton() // 单例缓存池
6.2 关键技术点
| 技术 | 作用 | Spring 中的应用 |
|---|---|---|
| Java 反射(Reflection) | 动态创建对象、调用方法 | Class.newInstance()、Method.invoke() |
| 注解解析(Annotation Processing) | 读取 @Component、@Autowired 等 | AnnotatedBeanDefinitionReader、ClassPathBeanDefinitionScanner |
| 字节码增强(CGLIB / JDK Proxy) | 创建代理对象 | AOP、@Async、@Transactional 的底层实现 |
| 类型匹配(Type Matching) | 根据类型查找 Bean | AutowireCandidateResolver |
| 缓存机制 | 避免重复创建 | 单例 Bean 存放在 ConcurrentHashMap<String, Object> 中 |
6.3 依赖注入的实现流程(简化)
// 1. 容器扫描包 → 找到 @Component 类 → 生成 BeanDefinition
// 2. 注册到 BeanDefinitionRegistry
// 3. 当调用 context.getBean("userService"):
// → 检查是否已存在单例 → 否则:
// → 1. 通过反射调用构造器 → 创建实例
// → 2. 遍历字段/方法 → 找 @Autowired
// → 3. 递归调用 getBean() 获取依赖
// → 4. 调用 setter / 构造器注入依赖
// → 5. 执行 @PostConstruct / InitializingBean
// → 6. 放入 singletonObjects 缓存
// → 7. 返回实例
✅ 核心思想:“你只管写 POJO,剩下的交给容器”
✅ 七、最佳实践与避坑指南
| 主题 | 建议 |
|---|---|
| 注入方式 | ✅ 优先构造器注入,避免字段注入 |
| 作用域 | ✅ 默认 singleton,有状态对象用 prototype,Web 用 request/session |
| 命名 | ✅ 使用语义化名称,避免 userServiceImpl,用 userService |
| @Qualifier | ✅ 与 @Primary 配合使用,避免过多注解 |
| 循环依赖 | ✅ Spring 支持 setter 循环依赖(三级缓存),但构造器循环依赖会报错 → 设计上应避免 |
| 初始化顺序 | ✅ 使用 @Order 或 @DependsOn 控制 Bean 初始化顺序 |
| 测试 | ✅ 使用 @Test + @ExtendWith(SpringExtension.class) + @SpringBootTest 或 @ContextConfiguration |
🚫 避坑:不要在 @PostConstruct 中使用 @Autowired 的 Bean
@Component
public class UserService {
@Autowired
private EmailService emailService;
@PostConstruct
public void init() {
emailService.sendWelcome(); // ❌ 可能为 null!
}
}
⚠️ 原因:
@PostConstruct在属性注入之后执行,理论上应该没问题。
但若emailService本身是@Lazy或@Scope("prototype"),且初始化顺序异常,可能为 null。
✅ 更安全做法:使用 ApplicationListener<ContextRefreshedEvent>:
@Component
public class UserService implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private EmailService emailService;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
emailService.sendWelcome(); // ✅ 容器完全启动后执行
}
}
✅ 八、总结:IoC 容器的“魔法”本质
| 问题 | Spring 的答案 |
|---|---|
| 如何创建对象? | 使用反射 + 构造器 |
| 如何注入依赖? | 扫描 @Autowired + 反射调用 setter / 构造器 |
| 如何管理生命周期? | 通过 BeanPostProcessor 和回调接口 |
| 如何避免重复创建? | 单例缓存(ConcurrentHashMap) |
| 如何支持多种配置? | XML / 注解 / JavaConfig → 统一转为 BeanDefinition |
| 如何让开发者写 POJO? | 容器接管一切,你只需声明“我要什么” |
✅ Spring IoC 容器的本质:
一个基于反射和注解解析的“对象工厂 + 依赖调度器”,它让开发者从“手动组装对象”解放出来,专注于业务逻辑。
3304

被折叠的 条评论
为什么被折叠?



