1. 单例Bean的生命周期全景图
在Spring框架中,单例Bean的创建过程就像工厂里的精密生产线,每个环节都有严格的质量控制。作为Spring容器管理的默认作用域对象,单例Bean从元数据解析到最终销毁的完整生命周期包含十几个关键阶段。我通过调试Spring 5.3.18源码,结合多年项目经验,梳理出最典型的初始化路线:
- 元数据加载阶段:容器启动时解析XML配置或注解,将Bean定义注册到BeanDefinitionRegistry
- 实例化前处理:执行InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()
- 构造器选择与调用:根据依赖情况决定使用无参构造或有参构造(自动装配时)
- 属性注入阶段:通过反射机制设置@Autowired字段或setter方法注入
- 初始化回调:依次执行BeanPostProcessor前置处理、@PostConstruct方法、InitializingBean.afterPropertiesSet()
- AOP代理生成:如有必要,通过CGLIB或JDK动态代理创建代理对象
- 加入单例池:最终成品存入DefaultSingletonBeanRegistry的singletonObjects Map
关键提示:Spring 5.x版本对循环依赖的处理策略有重大调整,使用三级缓存(singletonFactories、earlySingletonObjects、singletonObjects)解决setter注入的循环引用问题,但构造器注入的循环依赖仍会直接抛出BeanCurrentlyInCreationException。
2. 核心阶段深度解析
2.1 BeanDefinition的注册艺术
Spring容器启动时,配置元数据经过以下转换路径:
java复制// 注解配置的典型处理流程
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(registry);
reader.register(ConfigClass.class);
// XML配置的解析过程
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(registry);
xmlReader.loadBeanDefinitions("applicationContext.xml");
在这个过程中有几个容易踩坑的点:
- 使用@ComponentScan时,basePackages的路径写法会影响类扫描结果(建议使用全限定路径)
- @Configuration类中@Bean方法的相互调用会导致代理行为差异(需区分Full和Lite模式)
- 自定义BeanNameGenerator需注意命名冲突问题
2.2 实例化过程的精妙设计
当调用getBean()触发实例化时,AbstractAutowireCapableBeanFactory.createBean()方法会经历以下关键步骤:
java复制protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// 1. 解析Bean类
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
// 2. 前置处理(可能返回代理对象)
Object bean = resolveBeforeInstantiation(beanName, mbd);
if (bean != null) return bean;
// 3. 实际实例化
Object beanInstance = doCreateBean(beanName, mbd, args);
// 4. 返回最终Bean
return adaptBeanInstance(beanName, beanInstance, mbd);
}
特别要注意的是,Spring通过SmartInstantiationAwareBeanPostProcessor.determineCandidateConstructors()方法决定使用哪个构造器。当存在多个@Autowired构造器时,会优先选择参数多的构造器,但要求必须有一个且仅有一个required=true的构造器。
2.3 依赖注入的三种武器
Spring提供了灵活的依赖注入方式,各有适用场景:
| 注入方式 | 实现机制 | 优缺点比较 |
|---|---|---|
| 字段注入(@Autowired) | 反射直接设置字段值 | 代码简洁但难以单元测试 |
| setter注入 | 调用setter方法 | 适合可选依赖,支持重新配置 |
| 构造器注入 | 实例化时通过构造参数传入 | 线程安全,适合强制依赖 |
在Spring 4.3之后,如果类只有一个构造器,@Autowired注解可以省略。但要注意构造器注入的循环依赖问题无法通过三级缓存解决,必须调整设计。
3. 初始化回调的执行顺序
单例Bean的初始化过程就像精心编排的交响乐,各个回调方法按照严格顺序执行:
- BeanPostProcessor.postProcessBeforeInitialization()
- @PostConstruct注解方法
- InitializingBean.afterPropertiesSet()
- 自定义init-method
- BeanPostProcessor.postProcessAfterInitialization()
这个顺序在AbstractAutowireCapableBeanFactory.initializeBean()方法中有明确体现:
java复制protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
// 1. 执行Aware接口回调
invokeAwareMethods(beanName, bean);
// 2. 执行BeanPostProcessor前置处理
wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
// 3. 执行初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
// 4. 执行BeanPostProcessor后置处理
return applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
实际项目中常见的坑是:在@PostConstruct方法中调用其他Bean的方法,而此时那些Bean可能还未完成初始化。安全的做法是将交叉依赖的逻辑移到监听ContextRefreshedEvent事件中处理。
4. 单例Bean的线程安全实践
虽然Spring容器能保证单例Bean的单一实例,但线程安全仍需开发者自己保障。以下是典型场景的解决方案:
案例:带状态的Service类
java复制@Service
public class StatisticsService {
private final ConcurrentHashMap<String, AtomicInteger> counters = new ConcurrentHashMap<>();
public void increment(String key) {
counters.computeIfAbsent(key, k -> new AtomicInteger()).incrementAndGet();
}
}
线程安全设计模式对比表
| 模式 | 实现方式 | 适用场景 |
|---|---|---|
| 无状态设计 | 所有方法使用局部变量 | 最推荐的方式 |
| 方法同步 | 添加synchronized关键字 | 简单但性能影响大 |
| 并发容器 | 使用ConcurrentHashMap等 | 需要维护状态时 |
| ThreadLocal | 将状态绑定到线程 | 请求上下文信息传递 |
特别要注意@Async方法调用同类方法时的代理问题。由于Spring AOP基于代理实现,自调用不会经过代理,导致@Async失效。解决方法是将异步方法拆分到另一个Bean中。
5. 销毁阶段的正确姿势
单例Bean的销毁过程常常被忽视,但合理的资源释放同样重要。完整的销毁链包括:
- DestructionAwareBeanPostProcessor.postProcessBeforeDestruction()
- @PreDestroy注解方法
- DisposableBean.destroy()
- 自定义destroy-method
在ConfigurableApplicationContext.close()被调用时,这些方法会逆序执行。但现实中有几个常见问题:
- Web应用中忘记注册ContextLoaderListener导致销毁方法未执行
- 使用@PreDestroy的方法抛出异常中断销毁流程
- 在销毁方法中尝试访问已销毁的依赖Bean
可靠的实践方案是:
java复制@Service
public class ResourceHolder implements DisposableBean {
private final List<Closeable> resources = new ArrayList<>();
public void addResource(Closeable res) {
resources.add(res);
}
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void destroy() {
resources.forEach(res -> {
try {
res.close();
} catch (IOException e) {
logger.error("Close failed", e);
}
});
}
}
注意在销毁方法中避免使用事务操作,因为事务管理器可能已经关闭。对于需要事务的清理操作,应该在服务停止前显式调用。
