从 0 开始理解 Spring 的核心思想 —— IoC 和 DI(2)
本文深入探讨了Spring IoC容器中Bean的生命周期与作用域管理。首先分析了同类型Bean注入时的歧义问题,提出了三种解决方案:使用@Primary指定首选Bean、@Qualifier精确指定Bean名称以及@Resource按名称注入。随后详细解析了Bean的完整生命周期,从实例化、属性注入到初始化回调(Aware接口、@PostConstruct)直至销毁过程(@PreDestroy)
深入理解 Spring IoC 容器:Bean 的生命周期与作用域
前言:回顾与展望
在上一篇文章中,我们从“没有 Spring 的世界”开始,逐步理解了 IoC(控制反转)和 DI(依赖注入)的核心思想。我们知道了:
- IoC:将对象的创建和管理权交给容器
- DI:容器负责建立对象之间的依赖关系
- Spring 容器:一个强大的 IoC 容器,帮我们管理对象和依赖
文章最后留下了一个思考题:
如果我们有多个同类型的 Bean,Spring 该如何决定注入哪一个?
今天,我们就从这个思考题出发,深入 Spring IoC 容器的内部,一探究竟。同时,我们还将全面解析 Bean 的生命周期和作用域,让你对 Spring 的理解从“怎么用”进阶到“为什么这样设计”。
第一部分:同类型 Bean 的歧义问题
场景还原
假设我们有这样一个场景:系统中有两个用户服务实现,一个从数据库查询,一个从缓存查询。
@Component
public class DatabaseUserService implements UserService {
@Override
public User getUserById(Long id) {
System.out.println("从数据库查询用户: " + id);
return new User(id, "数据库用户");
}
}
@Component
public class CacheUserService implements UserService {
@Override
public User getUserById(Long id) {
System.out.println("从缓存查询用户: " + id);
return new User(id, "缓存用户");
}
}
@Component
public class OrderService {
@Autowired
private UserService userService; // 这里该注入哪个?
public void processOrder(Long userId) {
User user = userService.getUserById(userId);
System.out.println("处理订单: " + user.getName());
}
}
启动 Spring 容器时,你会看到这样的错误:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'com.example.UserService' available:
expected single matching bean but found 2: databaseUserService, cacheUserService
Spring 很诚实地说:“我不知道该注入哪个,你给我明确一下。”
解决方案一:@Primary——指定首选 Bean
@Component
@Primary // 标记为首选 Bean
public class DatabaseUserService implements UserService {
// ...
}
@Component
public class CacheUserService implements UserService {
// ...
}
这样,当遇到歧义时,Spring 会优先选择带有 @Primary 的 Bean。
解决方案二:@Qualifier——精确指定 Bean 名称
@Component
@Qualifier("database") // 给 Bean 起个名字
public class DatabaseUserService implements UserService {
// ...
}
@Component
@Qualifier("cache")
public class CacheUserService implements UserService {
// ...
}
@Component
public class OrderService {
@Autowired
@Qualifier("cache") // 精确指定要注入哪个
private UserService userService;
// ...
}
解决方案三:@Resource——按名称注入(JSR-250 标准)
@Component
public class OrderService {
@Resource(name = "databaseUserService") // 按 Bean 名称注入
private UserService userService;
// ...
}
深入思考:为什么 Spring 要这样设计?
这个设计体现了 Spring 的一个重要哲学:明确优于隐式。
当存在歧义时,Spring 选择报错而不是猜测,这避免了运行时出现难以排查的诡异问题。你必须明确告诉容器你的意图——这是一种保护机制。
第二部分:Bean 的生命周期全解析
理解了如何解决 Bean 的歧义问题,我们来看看 Bean 在 Spring 容器中从生到死的完整历程。
Bean 生命周期全景图
┌─────────────────────────────────────────────────────────────┐
│ Bean 生命周期全景图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 实例化前 │
│ ↓ │
│ 2. 实例化 (new) │
│ ↓ │
│ 3. 属性注入 │
│ ↓ │
│ 4. 初始化前 (Aware 接口回调) │
│ ↓ │
│ 5. 初始化 (InitializingBean, @PostConstruct, init-method) │
│ ↓ │
│ 6. 初始化后 (BeanPostProcessor) │
│ ↓ │
│ 7. 就绪使用 │
│ ↓ │
│ 8. 销毁前 (@PreDestroy, DisposableBean, destroy-method) │
│ ↓ │
│ 9. 销毁 │
│ │
└─────────────────────────────────────────────────────────────┘
实战演示:一个 Bean 的完整生命历程
让我们创建一个 Bean,实现各种生命周期回调接口,亲眼看看每个阶段是如何执行的。
@Component
public class LifecycleDemoBean implements BeanNameAware, BeanFactoryAware,
InitializingBean, DisposableBean {
private String beanName;
private BeanFactory beanFactory;
public LifecycleDemoBean() {
System.out.println("1. 构造函数执行:实例化 Bean");
}
@Autowired
private SomeDependency someDependency; // 假设有个依赖
@Autowired
public void setSomeDependency(SomeDependency someDependency) {
System.out.println("2. 属性注入:设置依赖");
this.someDependency = someDependency;
}
@Override
public void setBeanName(String name) {
System.out.println("3. BeanNameAware 回调:设置 Bean 名称为 " + name);
this.beanName = name;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
System.out.println("4. BeanFactoryAware 回调:获取 BeanFactory");
this.beanFactory = beanFactory;
}
@PostConstruct
public void postConstruct() {
System.out.println("5. @PostConstruct 注解方法执行");
}
@Override
public void afterPropertiesSet() {
System.out.println("6. InitializingBean.afterPropertiesSet() 执行");
}
@Bean(initMethod = "customInit")
public void customInit() {
System.out.println("7. 自定义 init-method 执行");
}
public void doSomething() {
System.out.println("Bean 正在执行业务方法");
}
@PreDestroy
public void preDestroy() {
System.out.println("8. @PreDestroy 注解方法执行");
}
@Override
public void destroy() {
System.out.println("9. DisposableBean.destroy() 执行");
}
@Bean(destroyMethod = "customDestroy")
public void customDestroy() {
System.out.println("10. 自定义 destroy-method 执行");
}
}
运行结果:
1. 构造函数执行:实例化 Bean
2. 属性注入:设置依赖
3. BeanNameAware 回调:设置 Bean 名称为 lifecycleDemoBean
4. BeanFactoryAware 回调:获取 BeanFactory
5. @PostConstruct 注解方法执行
6. InitializingBean.afterPropertiesSet() 执行
7. 自定义 init-method 执行
Bean 正在执行业务方法
8. @PreDestroy 注解方法执行
9. DisposableBean.destroy() 执行
10. 自定义 destroy-method 执行
生命周期各阶段详解
阶段1-2:实例化前与实例化
Spring 根据配置或注解找到 Bean 的定义,通过反射调用构造函数创建实例。
阶段3:属性注入
Spring 自动装配依赖(如 @Autowired、@Resource 等)。
阶段4:Aware 接口回调
Spring 提供了一系列 Aware 接口,让 Bean 获取容器资源:
public interface BeanNameAware { void setBeanName(String name); }
public interface BeanFactoryAware { void setBeanFactory(BeanFactory factory); }
public interface ApplicationContextAware { void setApplicationContext(ApplicationContext ctx); }
public interface EnvironmentAware { void setEnvironment(Environment env); }
// ... 等等
阶段5-7:初始化
三种初始化方式按顺序执行:
@PostConstruct注解方法InitializingBean接口的afterPropertiesSet()- 自定义 init-method(如
@Bean(initMethod = "customInit"))
阶段8:BeanPostProcessor——Spring 的魔法之源
这是 Spring 最强大的扩展点,AOP、事务管理等都是基于它实现的:
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof LifecycleDemoBean) {
System.out.println("BeanPostProcessor before:初始化前处理");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof LifecycleDemoBean) {
System.out.println("BeanPostProcessor after:初始化后处理,可以生成代理了");
// 这里可以返回代理对象,实现 AOP
}
return bean;
}
}
阶段9-10:销毁
三种销毁方式按顺序执行:
@PreDestroy注解方法DisposableBean接口的destroy()- 自定义 destroy-method
第三部分:Bean 的作用域详解
同一个 Bean 定义,可以有不同的“存在范围”。Spring 提供了多种作用域:
1. Singleton(单例)——默认作用域
@Component
@Scope("singleton") // 默认就是 singleton,可以不写
public class SingletonBean {
// 整个容器中只有一个实例
}
特点:
- 容器启动时创建(默认 eager 初始化)
- 所有地方使用同一个实例
- 线程不安全(要避免成员变量共享)
2. Prototype(原型)
@Component
@Scope("prototype")
public class PrototypeBean {
// 每次获取都创建新实例
}
特点:
- 每次
getBean()或注入时创建新实例 - 懒加载(使用时才创建)
- 适合有状态的对象
3. Request(请求级)
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestBean {
// 每个 HTTP 请求创建一个实例
}
适用场景:Web 应用中的请求上下文数据
4. Session(会话级)
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionBean {
// 每个 HTTP Session 创建一个实例
}
适用场景:用户登录信息、购物车等
5. Application(应用级)
@Component
@Scope("application")
public class ApplicationBean {
// 整个 ServletContext 生命周期一个实例
}
作用域选择指南
| 场景 | 推荐作用域 | 原因 |
|---|---|---|
| 无状态服务(Service、DAO) | Singleton | 节省内存,性能好 |
| 有状态对象 | Prototype | 避免线程安全问题 |
| 请求上下文数据 | Request | 请求间隔离 |
| 用户会话数据 | Session | 用户间隔离 |
常见陷阱:Singleton 注入 Prototype
@Component
@Scope("singleton")
public class SingletonService {
@Autowired
private PrototypeBean prototypeBean; // 问题:只注入一次
public void doSomething() {
prototypeBean.doSomething(); // 每次都是同一个实例!
}
}
解决方案一:ApplicationContext 手动获取
@Component
public class SingletonService {
@Autowired
private ApplicationContext context;
public void doSomething() {
PrototypeBean bean = context.getBean(PrototypeBean.class);
bean.doSomething();
}
}
解决方案二:ObjectFactory(推荐)
@Component
public class SingletonService {
@Autowired
private ObjectFactory<PrototypeBean> prototypeBeanFactory;
public void doSomething() {
PrototypeBean bean = prototypeBeanFactory.getObject();
bean.doSomething();
}
}
解决方案三:@Lookup 方法注入
@Component
public abstract class SingletonService {
@Lookup // Spring 会动态生成实现类
protected abstract PrototypeBean getPrototypeBean();
public void doSomething() {
PrototypeBean bean = getPrototypeBean();
bean.doSomething();
}
}
第四部分:源码级解读——Spring 容器内部工作原理
理解了 Bean 的生命周期和作用域,我们来看看 Spring 容器内部到底是怎么工作的。
核心接口设计
// 1. BeanFactory——最基础的容器
public interface BeanFactory {
Object getBean(String name);
<T> T getBean(Class<T> requiredType);
boolean containsBean(String name);
boolean isSingleton(String name);
boolean isPrototype(String name);
// ...
}
// 2. ApplicationContext——高级容器(继承自 BeanFactory)
public interface ApplicationContext extends BeanFactory {
// 增加了国际化、事件发布、资源访问等功能
}
简化版 IoC 容器实现原理
public class SimpleIoCContainer {
// 存储 Bean 定义
private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
// 存储单例 Bean 实例
private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
// 存储正在创建中的 Bean(用于检测循环依赖)
private Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>());
// 注册 Bean 定义
public void registerBeanDefinition(String name, BeanDefinition definition) {
beanDefinitionMap.put(name, definition);
}
// 获取 Bean(核心方法)
public Object getBean(String name) {
// 1. 先从缓存中获取单例
Object singleton = singletonObjects.get(name);
if (singleton != null) {
return singleton;
}
// 2. 检查是否循环依赖
if (singletonsCurrentlyInCreation.contains(name)) {
throw new RuntimeException("循环依赖 detected: " + name);
}
// 3. 获取 Bean 定义
BeanDefinition definition = beanDefinitionMap.get(name);
if (definition == null) {
return null;
}
// 4. 创建 Bean
return createBean(name, definition);
}
private Object createBean(String name, BeanDefinition definition) {
try {
// 标记正在创建
singletonsCurrentlyInCreation.add(name);
// 4.1 实例化
Object bean = definition.getBeanClass().newInstance();
// 4.2 属性注入(简化版)
populateBean(bean, definition);
// 4.3 初始化
initializeBean(bean, name);
// 4.4 如果是单例,放入缓存
if (definition.isSingleton()) {
singletonObjects.put(name, bean);
}
return bean;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
singletonsCurrentlyInCreation.remove(name);
}
}
private void populateBean(Object bean, BeanDefinition definition) {
// 遍历所有字段,找到 @Autowired 注解的字段进行注入
// 这是简化版,实际 Spring 要复杂得多
}
private void initializeBean(Object bean, String name) {
// 调用 Aware 接口
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(name);
}
// 调用 BeanPostProcessor 的前置处理
// ...
// 调用初始化方法
if (bean instanceof InitializingBean) {
((InitializingBean) bean).afterPropertiesSet();
}
// 调用 BeanPostProcessor 的后置处理
// ...
}
}
Spring 的三级缓存解决循环依赖
Spring 能解决大部分循环依赖问题,靠的是三级缓存:
public class DefaultSingletonBeanRegistry {
// 一级缓存:成品,完全初始化好的 Bean
private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
// 二级缓存:半成品,已经实例化但未初始化的 Bean
private Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
// 三级缓存:单例工厂,用于生成 Bean 的早期引用
private Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
}
工作原理:
- A 创建时,把自己提前暴露到三级缓存
- A 需要注入 B,去创建 B
- B 需要注入 A,从三级缓存拿到 A 的早期引用(放入二级缓存)
- B 创建完成
- A 继续完成初始化
第五部分:最佳实践与常见陷阱
最佳实践清单
✅ 优先使用构造函数注入
@Component
public class GoodPractice {
private final UserService userService;
public GoodPractice(UserService userService) {
this.userService = userService;
}
}
✅ 明确 Bean 的作用域
@Component
@Scope("prototype") // 显式声明,避免误解
public class StatefulBean {
private int state;
}
✅ 使用 @Primary 解决歧义
@Component
@Primary
public class MainUserService implements UserService { }
✅ 合理使用 @Qualifier
@Component
@Qualifier("cache")
public class CacheUserService implements UserService { }
常见陷阱及解决方案
❌ 陷阱1:字段注入导致难以测试
// 错误
@Component
public class OrderService {
@Autowired
private UserService userService; // 测试时无法替换
}
// 正确
@Component
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
}
}
❌ 陷阱2:Singleton 中使用 Prototype Bean
// 错误
@Component
public class SingletonService {
@Autowired
private PrototypeBean prototypeBean; // 永远只有一个
}
// 正确
@Component
public class SingletonService {
@Autowired
private ObjectFactory<PrototypeBean> factory;
public void doSomething() {
PrototypeBean bean = factory.getObject();
}
}
❌ 陷阱3:忘记处理多线程安全问题
// 错误
@Component // 默认 singleton
public class CounterService {
private int count = 0; // 线程不安全!
public void increment() {
count++;
}
}
// 正确
@Component
@Scope("prototype") // 每个线程有自己的实例
public class CounterService {
private int count = 0;
public void increment() {
count++;
}
}
第六部分:总结与下期预告
本节核心要点
| 知识点 | 核心内容 | 应用场景 |
|---|---|---|
| 同类型 Bean 歧义 | @Primary、@Qualifier、@Resource | 多实现类注入 |
| Bean 生命周期 | 实例化 → 注入 → 初始化 → 使用 → 销毁 | 理解容器工作原理 |
| Bean 作用域 | singleton、prototype、request、session | 选择合适的范围 |
| 循环依赖解决 | 三级缓存机制 | 理解 Spring 的设计智慧 |
| 最佳实践 | 构造注入、明确作用域 | 写出健壮的代码 |
一个小思考题
Spring 的 @Transactional 注解是如何工作的?它和 Bean 的生命周期有什么关系?
提示:想想 BeanPostProcessor 的 postProcessAfterInitialization 方法。
下期预告
《Spring AOP 解密——从动态代理到声明式事务》
我们将深入剖析 AOP 的实现原理,看看 Spring 是如何通过“织入”来增强 Bean 的功能,以及声明式事务到底是怎么一回事。
写在最后
通过这两篇文章,我们从“没有 Spring 的世界”出发,逐步深入到了 Spring 容器的内部工作原理。现在你应该能理解:
- 为什么需要 IoC:解耦、易测、职责单一
- DI 怎么实现:构造器、setter、字段注入
- Bean 的一生:从实例化到销毁的完整历程
- Spring 内部原理:三级缓存、BeanPostProcessor 等
Spring 的魔法不再神秘,它只是一套精心设计的、帮助我们管理对象的框架。
如果你觉得文章有帮助,欢迎点赞、收藏、分享。有任何疑问,请在评论区留言,我们一起讨论。
下期见!
更多推荐



所有评论(0)