Spring IoC 容器底层实现原理与 Bean 生命周期实战
版本基线 :Spring Framework 7.0.x(2025-11 GA,当前 7.0.10)/ Spring Boot 4.1.x JDK 基线 :Java 17 起,推荐 JDK 21/25 命名空间 :Spring 7 已彻底移除
javax.annotation,@PostConstruct/@PreDestroy/@Resource一律使用jakarta.annotation.*文中源码片段为便于阅读做了删减(去掉异常分支、日志、防御性判断),但保留了真实的方法名、字段名与调用顺序,可对照
spring-beans/spring-context源码阅读。
目录
- [本质:IoC 容器到底是什么](#本质:IoC 容器到底是什么 "#1-%E6%9C%AC%E8%B4%A8ioc-%E5%AE%B9%E5%99%A8%E5%88%B0%E5%BA%95%E6%98%AF%E4%BB%80%E4%B9%88")
- 容器的类型体系与职责分工
- 元数据层:BeanDefinition
- [启动主流程:refresh() 十二步](#启动主流程:refresh() 十二步 "#4-%E5%90%AF%E5%8A%A8%E4%B8%BB%E6%B5%81%E7%A8%8Brefresh-%E5%8D%81%E4%BA%8C%E6%AD%A5")
- [单例创建核心链路:doGetBean 到 doCreateBean](#单例创建核心链路:doGetBean 到 doCreateBean "#5-%E5%8D%95%E4%BE%8B%E5%88%9B%E5%BB%BA%E6%A0%B8%E5%BF%83%E9%93%BE%E8%B7%AFdogetbean-%E5%88%B0-docreatebean")
- 三级缓存与循环依赖
- [Bean 生命周期全景图与回调顺序](#Bean 生命周期全景图与回调顺序 "#7-bean-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%85%A8%E6%99%AF%E5%9B%BE%E4%B8%8E%E5%9B%9E%E8%B0%83%E9%A1%BA%E5%BA%8F")
- [生命周期各环节实战(12 个真实场景)](#生命周期各环节实战(12 个真实场景) "#8-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%90%84%E7%8E%AF%E8%8A%82%E5%AE%9E%E6%88%9812-%E4%B8%AA%E7%9C%9F%E5%AE%9E%E5%9C%BA%E6%99%AF")
- 扩展点选型决策表
- 常见陷阱与排查清单
- 一句话总结
1. 本质:IoC 容器到底是什么
剥掉所有设计模式的外衣,Spring IoC 容器在 Java 底层只做了五件事:
| 底层能力 | 解决的问题 | 对应 Java 技术 |
|---|---|---|
| 元数据建模 | 运行期才知道要创建哪些类 | BeanDefinition + 注解/字节码解析 |
| 反射实例化 | 打破编译期的 new 硬绑定 |
Class.forName / Constructor.newInstance |
| 反射注入 | 把依赖塞进私有字段 | Field.setAccessible(true) + Field.set |
| 缓存池 | 单例复用、解决循环依赖 | 三个 ConcurrentHashMap(三级缓存) |
| 回调扩展 | 让第三方在生命周期里插手 | BeanPostProcessor / BeanFactoryPostProcessor |
所谓"控制反转",反转的是对象创建与装配的归属权 :从 new 关键字,交给这套基于反射 + 元数据 + Map 的运行时工厂。
一句话概括:
用 Map 存配方,用反射造对象,用反射填依赖,用缓存保单例,用回调做增强。
2. 容器的类型体系与职责分工
2.1 BeanFactory 与 ApplicationContext
这是最容易混淆的一对概念。它们不是两种实现,而是能力分层:
arduino
BeanFactory (接口) ← 最底层:只定义"怎么拿 Bean"
↑
ListableBeanFactory ← 增加"批量枚举"(按类型查所有 Bean)
↑
ConfigurableBeanFactory ← 增加"可配置"(作用域、后置处理器、值解析器)
↑
AutowireCapableBeanFactory ← 增加"自动装配能力"
↑
AbstractBeanFactory
↑
AbstractAutowireCapableBeanFactory ← 真正实现 createBean / doCreateBean
↑
DefaultListableBeanFactory ← 注册中心:持有 beanDefinitionMap,Spring 的默认引擎
ApplicationContext 并不自己造 Bean,它持有一个 DefaultListableBeanFactory 并委托给它:
typescript
// AbstractApplicationContext
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
// GenericApplicationContext 中的真实引用
private final DefaultListableBeanFactory beanFactory;
@Override
public Object getBean(String name) {
return getBeanFactory().getBean(name); // 委托
}
@Override
public <T> Map<String, T> getBeansOfType(Class<T> type) {
return getBeanFactory().getBeansOfType(type); // 委托
}
}
2.2 两者的实际差异
| 维度 | BeanFactory | ApplicationContext |
|---|---|---|
| 单例预加载 | 懒加载,首次 getBean() 才创建 |
启动时预创建所有非懒加载单例 |
| 事件机制 | 无 | ApplicationEventPublisher + @EventListener |
| 国际化 | 无 | MessageSource |
| 资源加载 | 无 | ResourceLoader(classpath*: 等) |
| 自动注册 BPP | 需手动 addBeanPostProcessor |
自动识别 BeanPostProcessor / BeanFactoryPostProcessor |
| 环境抽象 | 无 | Environment / Profile |
结论 :ApplicationContext = BeanFactory + 企业服务 + 自动化的生命周期驱动。生产环境一律用 ApplicationContext。
2.3 关键类职责速查
| 类 | 职责 |
|---|---|
DefaultListableBeanFactory |
持有 beanDefinitionMap,实现 preInstantiateSingletons() |
AbstractAutowireCapableBeanFactory |
createBean / doCreateBean / populateBean / initializeBean |
DefaultSingletonBeanRegistry |
三级缓存、单例锁、getSingleton() |
AutowiredAnnotationBeanPostProcessor |
处理 @Autowired / @Value / @Inject |
CommonAnnotationBeanPostProcessor |
处理 @PostConstruct / @PreDestroy / @Resource |
AnnotationAwareAspectJAutoProxyCreator |
在初始化后生成 AOP 代理 |
ConfigurationClassPostProcessor |
最重要的 BFPP:解析 @Configuration / @ComponentScan / @Bean / @Import |
SimpleInstantiationStrategy |
无参构造反射实例化 |
CglibSubclassingInstantiationStrategy |
需要方法注入(@Lookup)时用 CGLIB 生成子类 |
3. 元数据层:BeanDefinition
3.1 三种配置形态 → 统一元数据
无论你用 XML、@Component 还是 @Bean,最终都被统一翻译成 BeanDefinition。这是容器能"运行期决定创建什么"的前提:
java
XML <bean> ──┐
@Component / @Service──┼──→ BeanDefinition ──→ beanDefinitionMap
@Bean 方法 ──┘ (配方)
3.2 BeanDefinition 关键字段
arduino
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
// 作用域
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
void setScope(String scope);
String getScope();
// 目标类(反射实例化的依据)
void setBeanClassName(String beanClassName);
String getBeanClassName();
// 构造参数值
ConstructorArgumentValues getConstructorArgumentValues();
// 属性值(XML <property> 或 @Bean 的属性)
MutablePropertyValues getPropertyValues();
// 初始化 / 销毁方法名
void setInitMethodName(String initMethodName);
void setDestroyMethodName(String destroyMethodName);
// 自动装配模式(byType / byName / constructor)
void setAutowireMode(int autowireMode);
// 懒加载 / 主候选 / 抽象
void setLazyInit(boolean lazyInit);
boolean isPrimary();
boolean isAbstract();
}
注意
getBeanClassName()返回的是String而非Class。这是刻意为之:容器可以在类尚未被 ClassLoader 加载 时就持有配方,实现懒解析,也支撑 Spring 7 的 AOT(编译期生成BeanDefinition而无需反射扫描)。
3.3 注册中心
typescript
// DefaultListableBeanFactory
private final Map<String, BeanDefinition> beanDefinitionMap =
new ConcurrentHashMap<>(256);
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
// 默认允许覆盖(Spring Boot 2.1+ 默认关闭,由 spring.main.allow-bean-definition-overriding 控制)
this.beanDefinitionMap.put(beanName, beanDefinition);
} else {
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
}
}
4. 启动主流程:refresh() 十二步
AbstractApplicationContext.refresh() 是整个容器的启动主心骨。理解这一段,等于理解了 Spring 启动的全部:
scss
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1. 准备:记录启动时间、设置 active 标志、初始化 PropertySource、校验必需环境变量
prepareRefresh();
// 2. 获取(或刷新)BeanFactory;XML 系在此处解析 XML 生成 BeanDefinition
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 装配 BeanFactory 基础设施:ClassLoader、SpEL 解析器、
// PropertyEditorRegistrar、ApplicationContextAwareProcessor、
// 注册环境 Bean(environment / systemProperties / systemEnvironment)、
// 忽略 ApplicationContextAware 等接口的自动注入
prepareBeanFactory(beanFactory);
try {
// 4. 留给子类的钩子(如 AbstractRefreshableWebApplicationContext 注册 Servlet 相关作用域)
postProcessBeanFactory(beanFactory);
// 5. ★ 执行 BeanFactoryPostProcessor:修改 BeanDefinition 元数据
invokeBeanFactoryPostProcessors(beanFactory);
// 6. ★ 注册 BeanPostProcessor(注意:只是注册,不执行)
registerBeanPostProcessors(beanFactory);
// 7. 初始化 MessageSource(国际化)
initMessageSource();
// 8. 初始化 ApplicationEventMulticaster(事件广播器)
initApplicationEventMulticaster();
// 9. 子类钩子:如 SpringBoot 在此启动内嵌 Web 服务器
onRefresh();
// 10. 注册 ApplicationListener 到广播器
registerListeners();
// 11. ★★ 实例化所有非懒加载单例 ------ Bean 生命周期的真正发生地
finishBeanFactoryInitialization(beanFactory);
// 12. 完成:初始化 LifecycleProcessor、触发 onRefresh 回调、
// 发布 ContextRefreshedEvent
finishRefresh();
}
catch (RuntimeException | Error ex) {
// 销毁已创建的单例,重置 active 标志
destroyBeans();
cancelRefresh(ex);
throw ex;
}
}
}
4.1 第 5 步与第 6 步的顺序为什么不能颠倒
这是设计上的硬约束:
BeanFactoryPostProcessor操作的是 BeanDefinition(元数据) ,作用于所有 Bean,必须在任何 Bean 实例化之前BeanPostProcessor操作的是 Bean 实例(对象)
如果先注册 BPP 再执行 BFPP,BFPP 可能会在扫描时新增 BeanDefinition,导致部分 Bean 无法被已注册的 BPP 处理。所以 Spring 强制:先跑完所有 BFPP,再统一收集并注册 BPP。
4.2 第 11 步:预实例化单例
scss
// DefaultListableBeanFactory
@Override
public void preInstantiateSingletons() throws BeansException {
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 非抽象 + 单例 + 非懒加载 → 立即创建
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
// FactoryBean 特殊处理:创建 FactoryBean 本身,必要时再创建 getObject() 产物
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName); // "&xxx"
if (bean instanceof SmartFactoryBean<?> smartFactoryBean
&& smartFactoryBean.isEagerInit()) {
getBean(beanName); // 立即创建真实产物
}
} else {
getBean(beanName); // ★ 触发完整生命周期
}
}
}
// ★ 所有单例创建完毕后,回调 SmartInitializingSingleton
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {
smartSingleton.afterSingletonsInstantiated();
}
}
}
关键点 :
SmartInitializingSingleton回调发生在全部单例创建完成之后。这是"全局就绪"语义最强的钩子,后面实战会重点用。
5. 单例创建核心链路:doGetBean 到 doCreateBean
5.1 doGetBean:三级缓存查找 + 递归创建
scss
// AbstractBeanFactory
protected <T> T doGetBean(String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly) {
String beanName = transformedBeanName(name); // 处理 "&" 前缀(FactoryBean)
// ① 尝试从缓存拿(含早期引用)
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 可能是 FactoryBean,需要取 getObject() 产物
return adaptBeanInstance(beanName, sharedInstance, requiredType);
}
// ② prototype 类型的循环依赖直接报错
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// ③ 父容器委派(Spring MVC 父子容器场景)
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
return parentBeanFactory.getBean(beanName, requiredType);
}
// ④ 取出合并后的 BeanDefinition(合并父定义的属性)
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// ⑤ 先创建当前 Bean 依赖的 depends-on Bean
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
getBean(dep); // 保证 dep 先于当前 Bean 创建
}
}
// ⑥ 按作用域创建
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args); // ★ 真正创建
} catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
return adaptBeanInstance(beanName, sharedInstance, requiredType);
}
else if (mbd.isPrototype()) {
Object prototypeInstance = createBean(beanName, mbd, args);
return adaptBeanInstance(beanName, prototypeInstance, requiredType);
}
else {
// request / session / 自定义作用域 → 从 Scope 对象获取
String scopeName = mbd.getScope();
Scope scope = this.scopes.get(scopeName);
return scope.get(beanName, () -> createBean(beanName, mbd, args));
}
}
5.2 getSingleton:单例锁 + 创建中标记
typescript
// DefaultSingletonBeanRegistry
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) { // ★ 全局单例锁,保证双重检查安全
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
beforeSingletonCreation(beanName); // ★ 标记"创建中",用于检测循环依赖
boolean newSingleton = false;
try {
singletonObject = singletonFactory.getObject(); // 回调 createBean
newSingleton = true;
}
finally {
afterSingletonCreation(beanName); // ★ 移除"创建中"标记
}
if (newSingleton) {
addSingleton(beanName, singletonObject); // 放入一级缓存,清理二三级
}
}
return singletonObject;
}
}
protected void beforeSingletonCreation(String beanName) {
if (!this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName); // ★ 循环依赖在此检出
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject); // 一级
this.singletonFactories.remove(beanName); // 清三级
this.earlySingletonObjects.remove(beanName); // 清二级
this.registeredSingletons.add(beanName);
}
}
singletonsCurrentlyInCreation 是一个 Set<String>,它既是循环依赖的检测器,也是 isSingletonCurrentlyInCreation() 判断"是否需要提前暴露早期引用"的依据。
5.3 doCreateBean:生命周期主干
scss
// AbstractAutowireCapableBeanFactory
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// ① 实例化:得到"空壳"对象(属性还没填)
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// ② 执行 MergedBeanDefinitionPostProcessor:预解析 @Autowired / @Value 元数据并缓存
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
// ③ 判断是否允许提前暴露早期引用(解决循环依赖的关键开关)
boolean earlySingletonExposure = (mbd.isSingleton()
&& this.allowCircularReferences
&& isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// ★ 把"能产生早期引用的工厂"放入三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Object exposedObject = bean;
// ④ 属性填充:依赖注入发生地
populateBean(beanName, mbd, instanceWrapper);
// ⑤ 初始化:Aware 回调 + BPP-before + init 方法 + BPP-after(AOP 在此)
exposedObject = initializeBean(beanName, exposedObject, mbd);
// ⑥ 循环依赖一致性校验
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false); // 不查三级
if (earlySingletonReference != null) {
if (exposedObject == bean) {
// 没生成代理:直接用早期引用(同一个对象,无差别)
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 生成了代理,且已有 Bean 注入了原始对象 → 引用不一致,抛异常
throw new BeanCurrentlyInCreationException(beanName, "...");
}
}
}
// ⑦ 注册 DisposableBean(用于容器关闭时的销毁回调)
registerDisposableBeanIfNecessary(beanName, bean, mbd);
return exposedObject;
}
5.4 createBeanInstance:反射实例化
scss
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
Class<?> beanClass = resolveBeanClass(mbd, beanName); // Class.forName 或已缓存的 Class
// ① 实例化前的短路机会:BPP 可以直接返回对象,跳过后续全部流程
Object bean = resolveBeforeInstantiation(beanName, mbd);
if (bean != null) {
return new BeanWrapperImpl(bean);
}
// ② 有 @Bean 工厂方法 → 调用该工厂方法
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
// ③ 构造器注入:@Autowired 标注的构造器 或 唯一有参构造器
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR
|| mbd.hasConstructorArgumentValues()) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// ④ 默认:无参构造反射 new
return instantiateBean(beanName, mbd);
}
private Object instantiateBean(String beanName, RootBeanDefinition mbd) {
// SimpleInstantiationStrategy
Constructor<?> ctor = mbd.getBeanClass().getDeclaredConstructor();
ReflectionUtils.makeAccessible(ctor);
return ctor.newInstance(); // ★ 等价于运行期的 new,但类型是运行期决定的
}
这就是"控制反转"的底层落点 :业务代码里没有
new,容器用Constructor.newInstance()在运行期替你创建。
5.5 populateBean:依赖注入
ini
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
// ① 实例化后、注入前的短路机会(返回 false 则跳过所有注入)
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
return;
}
}
}
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
// ② 按名字 / 按类型的自动装配(XML autowire="byName|byType")
int resolvedAutowireMode = mbd.getResolvedAutowireMode();
if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
// ... autowireByName / autowireByType
}
// ③ ★ 注解驱动的注入:@Autowired / @Value / @Resource / @Inject
if (hasInstantiationAwareBeanPostProcessors()) {
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) return;
pvs = pvsToUse;
}
}
// ④ 把 PropertyValues 应用到对象(XML <property> 走这里)
applyPropertyValues(beanName, mbd, bw, pvs);
}
@Autowired 的真正注入由 AutowiredAnnotationBeanPostProcessor 完成:
java
// AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement
@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
} else {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
// ★ 按类型解析,多个候选再按 @Primary / @Priority / 字段名匹配
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value); // ★★ 反射写入私有字段
}
}
为什么 private 字段能被注入? 因为 ReflectionUtils.makeAccessible(field) 内部调用了 field.setAccessible(true),关闭了 JVM 的访问权限检查(access check) 。这是反射 API 提供的能力,也是字段注入在 Java 语言层面成立的唯一原因。
性能提示 :
setAccessible在 JDK 9+ 模块系统下会受--illegal-access与模块导出限制,且首次调用有一定开销。Spring 通过ReflectionUtils内部缓存 +InjectionMetadata预解析来摊平这部分成本。
5.6 initializeBean:初始化与增强
scss
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
// ① Aware 接口回调
invokeAwareMethods(beanName, bean);
// BeanNameAware.setBeanName
// BeanClassLoaderAware.setBeanClassLoader
// BeanFactoryAware.setBeanFactory
Object wrappedBean = bean;
// ② BPP 前置处理(★ @PostConstruct 在此被触发)
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
// ③ 初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
// 3a. InitializingBean.afterPropertiesSet()
// 3b. 自定义 init-method(@Bean(initMethod="...") 或 XML init-method)
// ④ BPP 后置处理(★ AOP 代理在此生成)
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;
}
一个经常被误解的细节 :@PostConstruct 不是 Spring 自己的注解,它由 CommonAnnotationBeanPostProcessor 在前置处理阶段(第 ② 步)扫描并反射调用。因此它的执行顺序是:
kotlin
BPP.postProcessBeforeInitialization ──→ 内部触发 @PostConstruct
InitializingBean.afterPropertiesSet ──→ 其次
自定义 init-method ──→ 最后
5.7 AOP 代理的生成时机
typescript
// AbstractAutoProxyCreator
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// ★ 如果早期引用阶段已经代理过,这里就不再代理,保证引用一致
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
代理技术的选择:
| 情况 | 技术 | 底层 |
|---|---|---|
| 目标类实现了接口(且未强制 CGLIB) | JDK 动态代理 | Proxy.newProxyInstance + InvocationHandler |
| 目标类无接口 / 强制 CGLIB | CGLIB 子类 | 通过 ASM 直接生成 .class 字节码,覆写非 final 方法 |
Spring 7 变化 :7.0 起全局默认代理方式统一为 CGLIB (与 Spring Boot 的默认行为对齐,此前
@Async等处理器默认用 JDK 代理,行为不一致)。如需对单个 Bean 改用接口代理,用新注解@Proxyable(ProxyType.INTERFACES);也可声明名为AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME的ProxyConfigBean 来改全局默认值。另外 Spring 7 对 Java 24+ 应用启用了新的
ClassFileMetadataReader,改用 JDK 的 Class-File API(JEP 484)读取字节码元数据,替代内部裁剪版 ASM。这对应用透明。
6. 三级缓存与循环依赖
6.1 三个 Map 的角色
swift
// DefaultSingletonBeanRegistry
/** 一级:成品 Bean(已完成实例化 + 注入 + 初始化) */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 二级:早期半成品引用(已实例化,未初始化);可能是原始对象,也可能是代理 */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
/** 三级:对象工厂(能按需产出"早期引用") */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
查找顺序(getSingleton(beanName, allowEarlyReference)):
kotlin
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName); // ① 一级
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName); // ② 二级
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> factory = this.singletonFactories.get(beanName); // ③ 三级
if (factory != null) {
singletonObject = factory.getObject(); // ★ 按需生成早期引用
this.earlySingletonObjects.put(beanName, singletonObject); // 升二级
this.singletonFactories.remove(beanName); // 清三级
}
}
}
}
}
}
return singletonObject;
}
注意第 ② 步的
isSingletonCurrentlyInCreation(beanName)前置判断:只有正在创建中的 Bean 才允许提前暴露。正常获取一个不存在的 Bean,不会误取到半成品。
6.2 完整时序推演(A ↔ B 循环依赖,且 A 需要 AOP 代理)
less
getBean(A)
├─ 一级无 → beforeSingletonCreation(A) 标记 A 创建中
├─ instantiate(A) → A@raw(空壳,属性为空)
├─ earlySingletonExposure=true → addSingletonFactory(A, ObjectFactory)
│ 三级缓存: A → () -> getEarlyBeanReference(A)
├─ populateBean(A)
│ └─ 需要 B → getBean(B)
│ ├─ 一级无 → beforeSingletonCreation(B)
│ ├─ instantiate(B) → B@raw
│ ├─ 三级缓存: B → () -> getEarlyBeanReference(B)
│ ├─ populateBean(B)
│ │ └─ 需要 A → getSingleton(A, true)
│ │ ├─ 一级:无
│ │ ├─ 二级:无
│ │ ├─ 三级:命中 → factory.getObject()
│ │ │ └─ getEarlyBeanReference(A) → A@proxy(提前生成代理!)
│ │ │ └─ earlyProxyReferences.put(A, A@proxy) ← 记录,防重复代理
│ │ └─ 二级缓存: A → A@proxy,清三级 A
│ │ └─ field.set(B, A@proxy) ← B 持有的是代理,不是原始对象
│ ├─ initializeBean(B) → B 完成
│ └─ addSingleton(B) → B 进一级缓存
│ └─ field.set(A, B) ← A 拿到完整 B
├─ initializeBean(A)
│ └─ postProcessAfterInitialization(A)
│ └─ earlyProxyReferences.remove(A) == A@proxy ≠ A@raw
│ → 判定"已代理过",直接返回 A@raw,不再重复代理
├─ ⑥ 一致性校验:earlySingletonReference(A)=A@proxy ≠ exposedObject(A@raw)
│ 但 exposedObject == bean(原始对象本身),所以 exposedObject = A@proxy ★ 统一为代理
└─ addSingleton(A) → A@proxy 进一级缓存
结果 :B 里的 A 和容器里的 A 是同一个代理对象 A@proxy,AOP 完全生效。
6.3 为什么三级缓存必须是 ObjectFactory,而不是直接存对象
这是整个设计中最精巧的一点,本质是一个延迟决策(lazy decision) :
typescript
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp :
getBeanPostProcessorCache().smartInstantiationAware) {
// ★ 只有真正被别人提前引用时,才在这里决定是否生成代理
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
如果退化成二级缓存(实例化后立刻把对象放进去),会面临两难:
| 方案 | 问题 |
|---|---|
| 二级存原始对象 | 若 Bean 需要 AOP,B 注入的是原始对象 → 与最终成品不一致 → AOP 失效 |
| 二级存提前生成的代理 | 必须在每个 Bean 实例化后就跑一遍 AOP 判断 → 绝大多数 Bean 根本不会被循环依赖,白白付出代理扫描与生成的性能代价 |
三级缓存的 ObjectFactory 把"是否生成代理"这个决策推迟到真正有人来取早期引用的那一刻:
- 无循环依赖 → 三级缓存的
getObject()从不被调用 → 代理照常在初始化末尾生成,零额外开销 - 有循环依赖 → 只在被提前引用时才生成代理 → 且用
earlyProxyReferences记录,避免后续重复代理
一句话:三级缓存不是为了解决循环依赖,而是为了在解决循环依赖的同时,不破坏 AOP 代理的一致性,且不为不需要的 Bean 付出代价。
6.4 三种"破功"场景与解法
| 场景 | 报错/现象 | 根因 | 解法 |
|---|---|---|---|
| 构造器注入循环依赖 | BeanCurrentlyInCreationException |
实例化阶段就要拿到完整依赖,连"空壳 A"都 new 不出来,三级缓存在第一步就无东西可放 |
① 改 Setter/字段注入 ② 加 @Lazy 生成延迟代理 ③ 用 ObjectProvider<T> ④ 重构消除循环(推荐) |
| prototype 作用域循环依赖 | BeanCurrentlyInCreationException |
prototype 不进三级缓存,无法提前暴露 | 改为单例,或手工管理生命周期 |
@Async + 循环依赖 |
不报错,但异步失效 | @Async 的 AsyncAnnotationBeanPostProcessor 是普通 BPP,没有实现 getEarlyBeanReference,早期引用是原始对象 |
① 消除循环依赖(推荐) ② 把异步逻辑发成 ApplicationEvent ③ 用 ObjectProvider 延迟获取 |
架构建议 :在 Spring 7 + AOT/GraalVM 原生镜像的趋势下,循环依赖的代价越来越大(AOT 要求编译期确定 Bean 关系)。写出循环依赖不应被视为"技术能力",而应视为架构缺陷。Spring 的三级缓存是兜底,不是鼓励。
7. Bean 生命周期全景图与回调顺序
7.1 主流程图
scss
┌──────────────────────────────────────────────────────────────┐
│ 阶段一:容器启动准备 │
│ prepareRefresh → obtainFreshBeanFactory → prepareBeanFactory │
└──────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ 阶段二:元数据加工(无对象创建) │
│ BeanDefinitionRegistryPostProcessor.postProcessBeanDefinition│
│ Registry() ← 可注册新 BeanDefinition │
│ BeanFactoryPostProcessor.postProcessBeanFactory() │
│ ← 可修改已有 BeanDefinition │
│ registerBeanPostProcessors() ← 仅注册 BPP,不执行 │
└──────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ 阶段三:单个 Bean 的创建(preInstantiateSingletons 循环触发) │
│ │
│ ① InstantiationAwareBeanPostProcessor │
│ .postProcessBeforeInstantiation() ← 可短路返回自定义对象 │
│ ↓ │
│ ② 实例化:构造器反射 / 工厂方法 / CGLIB │
│ ↓ │
│ ③ MergedBeanDefinitionPostProcessor │
│ .postProcessMergedBeanDefinition() ← 预解析注入元数据 │
│ ↓ │
│ ④ addSingletonFactory() ← 三级缓存暴露 ObjectFactory │
│ ↓ │
│ ⑤ InstantiationAwareBeanPostProcessor │
│ .postProcessAfterInstantiation() ← 可中止注入 │
│ ↓ │
│ ⑥ InstantiationAwareBeanPostProcessor │
│ .postProcessProperties() ← ★ @Autowired / @Value / @Resource│
│ ↓ │
│ ⑦ Aware 回调 │
│ BeanNameAware → BeanClassLoaderAware → BeanFactoryAware │
│ → EnvironmentAware → ApplicationContextAware ... │
│ ↓ │
│ ⑧ BeanPostProcessor.postProcessBeforeInitialization() │
│ ★ @PostConstruct 在此被 CommonAnnotationBeanPostProcessor │
│ 反射调用 │
│ ↓ │
│ ⑨ InitializingBean.afterPropertiesSet() │
│ ↓ │
│ ⑩ 自定义 init-method │
│ ↓ │
│ ⑪ BeanPostProcessor.postProcessAfterInitialization() │
│ ★ AOP 代理在此生成 │
│ ↓ │
│ ⑫ 放入一级缓存 singletonObjects │
└──────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ 阶段四:全部就绪 │
│ SmartInitializingSingleton.afterSingletonsInstantiated() │
│ LifecycleProcessor.onRefresh() / SmartLifecycle.start() │
│ ContextRefreshedEvent │
│ ApplicationRunner / CommandLineRunner │
│ ApplicationReadyEvent │
└──────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ 阶段五:运行期(Bean 被使用) │
└──────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────┐
│ 阶段六:销毁(context.close() / JVM shutdown hook) │
│ @PreDestroy(DestructionAwareBeanPostProcessor) │
│ DisposableBean.destroy() │
│ 自定义 destroy-method │
│ SmartLifecycle.stop() │
│ ContextClosedEvent │
└──────────────────────────────────────────────────────────────┘
7.2 完整回调顺序表
| # | 环节 | 扩展点 / 接口 | 此时 Bean 的状态 |
|---|---|---|---|
| 1 | 注册 BD | BeanDefinitionRegistryPostProcessor |
无对象 |
| 2 | 修改 BD | BeanFactoryPostProcessor |
无对象 |
| 3 | 实例化前 | InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation |
无对象,可短路 |
| 4 | 实例化 | 构造器 | 空壳 |
| 5 | 合并 BD 后处理 | MergedBeanDefinitionPostProcessor |
空壳 |
| 6 | 暴露早期引用 | 三级缓存 ObjectFactory |
空壳 |
| 7 | 实例化后 | postProcessAfterInstantiation |
空壳,可中止注入 |
| 8 | 属性注入 | postProcessProperties(@Autowired / @Value) |
依赖已就位 |
| 9 | Aware 回调 | BeanNameAware / BeanFactoryAware / ApplicationContextAware |
依赖已就位 |
| 10 | 初始化前 | BeanPostProcessor#postProcessBeforeInitialization( @PostConstruct) |
依赖已就位 |
| 11 | 初始化中 | InitializingBean#afterPropertiesSet |
依赖已就位 |
| 12 | 初始化方法 | init-method |
依赖已就位 |
| 13 | 初始化后 | BeanPostProcessor#postProcessAfterInitialization(AOP 代理) |
可能被替换成代理 |
| 14 | 全局就绪 | SmartInitializingSingleton |
全部单例已就绪 |
| 15 | 生命周期启动 | SmartLifecycle#start(按 getPhase() 排序) |
容器刷新完成 |
| 16 | 应用就绪 | ApplicationRunner / CommandLineRunner |
完全就绪 |
| 17 | 销毁前 | @PreDestroy |
运行中 |
| 18 | 销毁中 | DisposableBean#destroy |
运行中 |
| 19 | 销毁方法 | destroy-method |
运行中 |
8. 生命周期各环节实战(12 个真实场景)
这一节回答核心问题:每个环节到底该在什么业务里用。每个场景给出"为什么选这个环节"的理由,因为选错环节是生产事故的高发区。
场景 1:BeanDefinitionRegistryPostProcessor ------ 批量动态注册 Bean
业务问题:MyBatis 有几十个 Mapper 接口,每个都只有接口没有实现类,怎么变成 Bean?
为什么选这个环节 :这是唯一能在容器实例化任何 Bean 之前,批量往容器里塞 BeanDefinition 的时机。
typescript
// MyBatis 的 MapperScannerConfigurer 就是这个思路的简化版
public class MapperScannerPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.addIncludeFilter(new AnnotationTypeMapperFilter());
scanner.scan("com.example.**.mapper"); // 扫描包
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) {
// 无需操作实例,留空
}
}
// 扫描器内部:把接口注册成 MapperFactoryBean(FactoryBean 模式)
class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> holders = super.doScan(basePackages);
for (BeanDefinitionHolder holder : holders) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 关键:把 beanClass 换成 FactoryBean,并把原接口名作为构造参数
definition.getConstructorArgumentValues()
.addGenericArgumentValue(definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);
}
return holders;
}
}
FactoryBean是这里的点睛之笔:getBean("userMapper")返回的是MapperFactoryBean.getObject()的代理产物;getBean("&userMapper")才返回 FactoryBean 本身。
同类应用 :OpenFeign 的 @FeignClient 注册、Spring Cloud 的 @LoadBalanced RestTemplate 标记、Dubbo 的 @DubboReference。
场景 2:Spring 7 的 BeanRegistrar ------ 更干净的编程式注册
业务问题 :需要根据环境/配置条件化地注册多个 Bean ,且要避免 BeanFactoryPostProcessor 的陷阱(见场景 10)。
为什么选这个环节 :Spring 7 新增的 BeanRegistrar 就是为这类场景设计的一等公民,替代手写 BFPP。
kotlin
class ClientsRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
// 无条件注册
registry.registerBean("httpClientConfig", HttpClientConfig.class);
// 条件注册
if (env.matchesProfiles("eu")) {
registry.registerBean(RegionRouter.class,
spec -> spec.supplier(ctx -> new RegionRouter("eu-west-1")));
}
// 带别名的注册
registry.registerBean("primaryDs", DataSource.class,
spec -> spec.supplier(ctx -> buildDataSource(env))
.primary()
.lazyInit());
}
}
@Configuration
@Import(ClientsRegistrar.class)
class AppConfig { }
与 BFPP 的关键区别 :BeanRegistrar 在 ConfigurationClassPostProcessor 处理 @Import 时执行,不会提前触发普通 Bean 的创建,天然规避了 BFPP 的经典陷阱。Spring 7 新项目优先用它。
场景 3:BeanFactoryPostProcessor ------ 配置解密 / 占位符解析
业务问题 :数据库密码在配置中心是密文(ENC(xxx)),需要在注入前解密。
为什么选这个环节 :BFPP 能修改 BeanDefinition 的 PropertyValues,让后续注入直接拿到明文。
typescript
@Component
public class DecryptingBeanFactoryPostProcessor implements BeanFactoryPostProcessor, EnvironmentAware {
private Environment environment;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
String[] names = beanFactory.getBeanDefinitionNames();
for (String name : names) {
BeanDefinition bd = beanFactory.getBeanDefinition(name);
for (PropertyValue pv : bd.getPropertyValues().getPropertyValueList()) {
Object value = pv.getValue();
if (value instanceof String s && s.startsWith("ENC(") && s.endsWith(")")) {
String cipher = s.substring(4, s.length() - 1);
pv.setConvertedValue(AesUtil.decrypt(cipher, getKey())); // 原地替换为明文
}
}
}
}
@Override
public void setEnvironment(Environment environment) { this.environment = environment; }
}
注意 :这个实现只能处理
BeanDefinition里显式声明的属性值。如果用@Value("${db.password}")注解注入,值解析发生在postProcessProperties阶段,BFPP 改不到。那种情况应该用PropertySourcesPlaceholderConfigurer自定义PropertySource,或自定义ConversionService。
场景 4:postProcessBeforeInstantiation ------ 短路创建,返回自定义对象
业务问题 :RPC 消费者 Bean 数量庞大(几百个 @RpcReference 接口),启动时全部建连太慢;希望注入的是一个惰性代理,首次调用时才建连。
为什么选这个环节 :这是唯一能在构造器执行之前就返回一个完全不同对象的时机,能彻底跳过 Spring 的实例化与初始化流程。
kotlin
@Component
public class RpcReferencePostProcessor implements InstantiationAwareBeanPostProcessor {
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
throws BeansException {
// 只处理带 @RpcReference 标记的接口
if (!beanClass.isInterface() || !beanClass.isAnnotationPresent(RpcReference.class)) {
return null; // 返回 null = 走正常流程
}
RpcReference ann = beanClass.getAnnotation(RpcReference.class);
// ★ 直接返回 JDK 动态代理,Spring 不再实例化/注入/初始化这个 Bean
return Proxy.newProxyInstance(
beanClass.getClassLoader(),
new Class<?>[]{ beanClass },
(proxy, method, args) -> {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(proxy, args);
}
// 首次调用才真正解析服务地址、建立连接
RpcInvoker invoker = ServiceRegistry.lookup(ann.serviceName());
return invoker.invoke(method, args);
});
}
}
同类应用 :单元测试中注入 Mock 对象、AOP 的 TargetSource 机制(AbstractAutoProxyCreator 内部就是这个思路)、Spring 的 @Lookup 方法注入。
场景 5:postProcessProperties ------ 自定义注解注入
业务问题 :想做一个自己的注入注解 @ConfigValue("key"),从配置中心(Nacos/Apollo)取值并支持自动刷新。
为什么选这个环节 :这是 Spring 官方推荐的自定义注入 扩展点,比自己扫字段再 field.set 更规范------它能复用 Spring 的类型转换、依赖解析和元数据缓存。
scss
@Component
public class ConfigValuePostProcessor implements InstantiationAwareBeanPostProcessor {
@Autowired
private ConfigurableBeanFactory beanFactory;
@Autowired
private NacosConfigService nacos;
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
ReflectionUtils.doWithLocalFields(bean.getClass(), field -> {
ConfigValue ann = field.getAnnotation(ConfigValue.class);
if (ann == null) return;
ReflectionUtils.makeAccessible(field);
// 初始值
String raw = nacos.getConfig(ann.value(), ann.defaultValue());
field.set(bean, beanFactory.getTypeConverter()
.convertIfNecessary(raw, field.getType()));
// 注册配置变更监听,实现热更新
nacos.addListener(ann.value(), newValue ->
field.set(bean, beanFactory.getTypeConverter()
.convertIfNecessary(newValue, field.getType())));
});
return pvs;
}
}
场景 6:@PostConstruct ------ 本 Bean 内部资源初始化
业务问题:一个本地字典缓存组件,需要在 Bean 自身依赖就位后加载字典数据。
为什么选这个环节 :@PostConstruct 执行时,当前 Bean 的所有注入已完成,可以安全使用自己的依赖。
typescript
@Component
public class DictCache {
@Autowired
private DictMapper dictMapper;
private volatile Map<String, String> cache = Map.of();
// Spring 7 起:import jakarta.annotation.PostConstruct;
@PostConstruct
public void init() {
Map<String, String> loaded = dictMapper.selectAll().stream()
.collect(Collectors.toMap(Dict::getCode, Dict::getName));
this.cache = Map.copyOf(loaded);
log.info("字典缓存加载完成,共 {} 条", loaded.size());
}
public String getName(String code) {
return cache.get(code);
}
}
⚠️ 重要局限 :
@PostConstruct只保证当前 Bean 的依赖已注入,不保证其他 Bean 已初始化 。如果init()里调用了一个尚未创建的 Bean 的方法,可能拿到半初始化状态的对象。 需要"全局就绪"语义时,请改用场景 8 的SmartInitializingSingleton或场景 9 的ApplicationRunner。
场景 7:InitializingBean / init-method ------ 配置校验与第三方客户端启动
业务问题 :Netty 服务端组件的端口、线程数配置不能靠人肉检查,错了要在启动阶段直接失败(fail-fast),而不是运行到一半崩。
为什么选这个环节 :afterPropertiesSet() 的语义就是"所有属性设置完毕后的校验与启动",抛异常会直接中断容器启动。
java
@Component
public class NettyServerComponent implements InitializingBean, DisposableBean {
@Value("${netty.port:9000}")
private int port;
@Value("${netty.boss-threads:1}")
private int bossThreads;
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
private Channel serverChannel;
@Override
public void afterPropertiesSet() throws Exception {
// ★ fail-fast 校验
Assert.isTrue(port > 1024 && port < 65535, "netty.port 必须在 (1024, 65535) 区间");
Assert.isTrue(bossThreads > 0, "netty.boss-threads 必须大于 0");
bossGroup = new NioEventLoopGroup(bossThreads);
workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerInitializer());
ChannelFuture f = b.bind(port).sync();
serverChannel = f.channel();
log.info("Netty 服务启动于端口 {}", port);
}
@Override
public void destroy() { /* 见场景 11 */ }
}
InitializingBean vs init-method 怎么选:
InitializingBean--- 与 Spring API 强耦合,不推荐用于业务代码@Bean(initMethod = "start")--- 推荐 ,尤其集成第三方类时(无法改源码加注解)@PostConstruct--- 推荐用于自研 Bean,标准注解,无 Spring 耦合
场景 8:SmartInitializingSingleton ------ 全局就绪后的缓存预热
业务问题 :商品详情页的热点缓存,预热时必须访问多个其他 Bean (价格服务、库存服务、促销服务)。用 @PostConstruct 有风险------那些 Bean 可能还没创建。
为什么选这个环节 :afterSingletonsInstantiated() 在 preInstantiateSingletons() 的第二轮循环 中被调用,此时所有非懒加载单例都已创建完毕。这是"全局就绪"语义最强的回调。
scss
@Component
public class HotItemCacheWarmer implements SmartInitializingSingleton {
@Autowired
private PriceService priceService;
@Autowired
private StockService stockService;
@Autowired
private PromotionService promotionService;
@Autowired
private RedisTemplate<String, Object> redis;
@Override
public void afterSingletonsInstantiated() {
// ★ 此刻所有依赖的 Bean 都已 100% 就绪,可以安全调用
List<Long> hotItemIds = loadHotItemIds();
hotItemIds.parallelStream().forEach(id -> {
ItemSnapshot snapshot = new ItemSnapshot();
snapshot.setPrice(priceService.getPrice(id));
snapshot.setStock(stockService.getStock(id));
snapshot.setPromotion(promotionService.getActivePromotion(id));
redis.opsForValue().set("item:snapshot:" + id, snapshot, Duration.ofMinutes(30));
});
log.info("热点商品缓存预热完成,共 {} 个", hotItemIds.size());
}
}
对比:@PostConstruct 在这里会有什么问题?
假设 PromotionService 本身还有一个 @PostConstruct 且它依赖 HotItemCacheWarmer 之外的东西------预热时 promotionService 可能处于"注入完成但未初始化"的状态,调用它的方法可能 NPE 或返回默认值。SmartInitializingSingleton 彻底规避了这个风险。
场景 9:SmartLifecycle ------ 控制启动/停止顺序的组件
业务问题 :MQ 消费者必须在所有业务 Bean 就绪后 才开始消费,否则消息进来时依赖的服务还没初始化,直接报错。容器关闭时,消费者也要先停止再关数据源。
为什么选这个环节 :Lifecycle 提供 start()/stop(),SmartLifecycle 额外提供 getPhase() 做相位排序:
phase越小越先start(),越后stop()- 默认
SmartLifecycle的 phase =Integer.MAX_VALUE(最后启动) isAutoStartup()返回 true 时容器刷新完成自动启动
typescript
@Component
public class OrderMessageConsumer implements SmartLifecycle {
private volatile boolean running = false;
@Autowired
private RocketMQListenerContainer container;
@Autowired
private OrderService orderService; // 必须已就绪
@Override
public void start() {
if (running) return;
// 前置确认:依赖 Bean 确实可用
Assert.notNull(orderService, "OrderService 未就绪");
container.start();
running = true;
log.info("订单消息消费者已启动");
}
@Override
public void stop() {
// ★ 优雅停止:先拒绝新消息,再等待在途消息处理完
container.pause();
awaitInFlightMessages(30, TimeUnit.SECONDS);
container.shutdown();
running = false;
log.info("订单消息消费者已优雅停止");
}
@Override
public boolean isRunning() { return running; }
@Override
public boolean isAutoStartup() { return true; }
@Override
public int getPhase() {
// 比默认值小一点 → 比大多数组件先启动
// 数据源、连接池通常由 Spring Boot 管理,phase 更靠前
return Integer.MAX_VALUE - 1000;
}
}
典型应用:MQ 消费者、定时任务调度器、注册中心心跳、内嵌 Netty/Grpc 服务。
场景 10:BeanPostProcessor ------ 统一处理自定义注解(AOP 前置)
业务问题 :想做一个 @DistributedLock 注解,标在方法上自动加分布式锁。
为什么选这个环节 :postProcessAfterInitialization 是生成代理的标准时机,此时 Bean 已完成初始化,可以安全包装。
less
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
String key();
long waitSeconds() default 3;
long leaseSeconds() default 30;
}
ini
@Component
public class DistributedLockPostProcessor implements BeanPostProcessor {
@Autowired
private RedissonClient redisson;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
// 快速判断:类里有没有标注该注解的方法,没有直接返回(性能)
if (!hasLockedMethod(targetClass)) {
return bean;
}
return ProxyFactory.getProxy(targetClass, (MethodInterceptor) invocation -> {
Method method = invocation.getMethod();
DistributedLock ann = AnnotationUtils.findAnnotation(method, DistributedLock.class);
if (ann == null) {
return invocation.proceed();
}
// 支持 SpEL 的锁 key
String lockKey = parseSpel(ann.key(), method, invocation.getArguments());
RLock lock = redisson.getLock(lockKey);
boolean acquired = lock.tryLock(ann.waitSeconds(), ann.leaseSeconds(), TimeUnit.SECONDS);
if (!acquired) {
throw new BizException("获取锁失败,请重试: " + lockKey);
}
try {
return invocation.proceed();
} finally {
lock.unlock();
}
});
}
}
⚠️ 致命陷阱 :
BeanPostProcessor自身不能依赖普通 Bean 并用字段注入 。因为 BPP 必须在所有普通 Bean 之前实例化,此时AutowiredAnnotationBeanPostProcessor还没生效,注入会失败或静默为 null。症状是 Spring Boot 启动日志的警告:
rustBean 'xxxPostProcessor' of type [...] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)解法 :① 改用构造器注入 ② 实现
ApplicationContextAware手动getBean③ 用ObjectProvider延迟获取 ④@Lazy注入。
场景 11:@PreDestroy / DisposableBean ------ 优雅关闭资源
业务问题:容器关闭时,线程池里的任务不能粗暴中断,MQ 连接要优雅断开,注册中心要先注销再关闭。
为什么选这个环节 :这是官方提供的销毁钩子,@PreDestroy 在销毁链最前,能确保所有连接还可用。
java
@Component
public class GracefulResourceHolder implements DisposableBean {
private final ExecutorService bizPool = new ThreadPoolExecutor(
8, 8, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10_000),
new ThreadFactoryBuilder().setNameFormat("biz-pool-%d").build());
@Autowired
private ServiceRegistry registry; // 注册中心客户端
// Spring 7 起:import jakarta.annotation.PreDestroy;
@PreDestroy
public void deregister() {
// ★ 最先执行:此时所有连接仍可用,先把自己从注册中心摘掉
// (摘掉后上游流量会先切走,再关本地资源,实现无损下线)
try {
registry.deregister(currentInstanceId());
Thread.sleep(5_000); // 等待上游感知,时间要大于注册中心心跳间隔
} catch (Exception e) {
log.warn("注销注册中心失败,继续关闭流程", e);
}
}
@Override
public void destroy() throws Exception {
// ★ 后执行:优雅关闭线程池
bizPool.shutdown();
if (!bizPool.awaitTermination(30, TimeUnit.SECONDS)) {
List<Runnable> dropped = bizPool.shutdownNow();
log.warn("线程池超时未关闭,丢弃 {} 个任务", dropped.size());
}
log.info("业务线程池已关闭");
}
}
销毁顺序速记 :@PreDestroy → DisposableBean.destroy() → destroy-method。
Spring Boot 优雅关闭配套配置(必须开启才生效):
yaml
server:
shutdown: graceful # 内嵌 Web 服务器等待请求处理完
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
场景 12:Aware 接口 ------ 拿到容器基础设施(谨慎使用)
业务问题:一个通用事件工具,需要在非 Bean 管理的类里发布 Spring 事件。
为什么选这个环节 :ApplicationContextAware 在 ApplicationContextAwareProcessor(一个普通的 BeanPostProcessor)中被回调,是获取容器句柄的标准方式。
typescript
@Component
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext CONTEXT;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
CONTEXT = applicationContext;
}
public static <T> T getBean(Class<T> type) {
Assert.notNull(CONTEXT, "ApplicationContext 尚未初始化");
return CONTEXT.getBean(type);
}
public static void publish(Object event) {
CONTEXT.publishEvent(event);
}
public static String resolvePlaceholder(String key) {
return CONTEXT.getEnvironment().getProperty(key);
}
}
⚠️ 这是反模式 。静态持有
ApplicationContext会:
- 把 IoC 又变回"主动查找"(Service Locator),破坏控制反转
- 让依赖关系在编译期不可见,重构时极易踩坑
- 单元测试时必须先启动容器,难以 mock
更好的方案:
- 需要延迟获取 →
ObjectProvider<T>- 需要解耦通信 →
ApplicationEventPublisher直接注入- 需要多个实现 → 注入
List<Interface>或Map<String, Interface>只在框架代码 / 遗留系统适配中使用,业务代码应避免。
9. 扩展点选型决策表
| 你的需求 | 首选扩展点 | 备选 | 千万别用 |
|---|---|---|---|
| 批量注册 Bean(接口转代理) | BeanDefinitionRegistryPostProcessor 或 Spring 7 BeanRegistrar |
ImportBeanDefinitionRegistrar |
手动 new 后塞进 Map |
| 修改已有 BeanDefinition | BeanFactoryPostProcessor |
--- | 在 BPP 里改 BD |
| 替换整个 Bean 的创建 | postProcessBeforeInstantiation |
FactoryBean |
--- |
| 自定义注入注解 | postProcessProperties |
字段反射 + field.set |
在 postProcessAfterInitialization 里注入 |
| 本 Bean 内部初始化 | @PostConstruct |
@Bean(initMethod=...) |
构造器里做重活 |
| 配置校验 / fail-fast | InitializingBean / @PostConstruct + Assert |
--- | 等到运行时才报错 |
| 依赖多个 Bean 的全局初始化 | SmartInitializingSingleton |
ApplicationRunner |
@PostConstruct |
| 需要启停顺序控制 | SmartLifecycle(getPhase()) |
@EventListener(ContextRefreshedEvent) |
普通 @PostConstruct |
| 给 Bean 加代理 / 增强 | postProcessAfterInitialization |
@Aspect 切面 |
--- |
| 资源清理 / 优雅下线 | @PreDestroy + DisposableBean |
ContextClosedEvent |
JVM Runtime.addShutdownHook |
| 应用完全就绪后执行 | ApplicationRunner / CommandLineRunner |
ApplicationReadyEvent |
ContextRefreshedEvent(会触发多次) |
三个"就绪"钩子的精确区别
| 钩子 | 触发时机 | 触发次数 | 事务/Web 容器状态 |
|---|---|---|---|
SmartInitializingSingleton |
所有单例创建完毕,但Web 服务器可能未启动 | 1 次 | 无事务上下文 |
ContextRefreshedEvent |
容器刷新完成 | 可能多次(子容器刷新也会触发) | Web 服务器已启动 |
ApplicationRunner / ApplicationReadyEvent |
Spring Boot 完全就绪 | 1 次 | Web 服务器已监听端口 |
结论 :Spring Boot 应用做启动预热,优先用 ApplicationReadyEvent 或 ApplicationRunner。
10. 常见陷阱与排查清单
陷阱 1:自调用导致 AOP 失效
typescript
@Service
public class OrderService {
public void createOrder() {
// ❌ this 是原始对象,不是代理!事务/缓存/锁全都不生效
this.deductStock();
}
@Transactional
public void deductStock() { ... }
}
根因 :Spring AOP 是代理模式,只有通过代理对象 调用才会走拦截器链。this.xxx() 绕过了代理。
解法(按推荐度排序):
- 拆成两个 Bean(最干净,也顺便消除了循环依赖隐患)
- 自注入代理:
@Autowired private OrderService self;然后self.deductStock() AopContext.currentProxy()(需@EnableAspectJAutoProxy(exposeProxy = true),侵入性强)
陷阱 2:private / final / static 方法上的 AOP 失效
private:CGLIB 无法覆写(子类访问不到)final方法 /final类:CGLIB 无法继承覆写static:不属于实例
Spring 7 提醒 :7.0 起默认代理改为 CGLIB,
final方法的坑会比以前更容易踩到。升级时需检查。
陷阱 3:BeanPostProcessor 过早依赖普通 Bean
见场景 10。症状是启动日志出现 is not eligible for getting processed by all BeanPostProcessors。解决用构造器注入或 ObjectProvider。
陷阱 4:单例里注入 prototype 失效
kotlin
@Service
public class OrderService {
@Autowired
private OrderCommand command; // ❌ prototype 只注入了一次,永远是同一个实例
}
解法:
ObjectProvider<OrderCommand>+getObject()(推荐)@Lookup方法注入(CGLIB 生成子类)@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)(作用域代理)
陷阱 5:构造器注入的循环依赖
typescript
@Service
public class A {
public A(B b) { ... } // ❌ 启动直接失败
}
@Service
public class B {
public B(A a) { ... }
}
解法:
- 重构(推荐):抽出一个 C,让 A、B 都依赖 C
- 改 Setter 注入
@Lazy注解在构造参数上:public A(@Lazy B b)------ 注入一个延迟代理ObjectProvider<B>:真正用到时才getObject()
陷阱 6:在 @PostConstruct 里访问未就绪的 Bean
见场景 6 vs 场景 8。需要全局就绪语义时改用 SmartInitializingSingleton。
陷阱 7:@Transactional 的回滚误解
- 默认只在 unchecked exception (
RuntimeException/Error)时回滚,checked exception 会提交 - 方法内部
try-catch吞掉异常 → 照常提交 - 内层
REQUIRED方法失败标记 rollback-only,外层 catch 后正常返回 →UnexpectedRollbackException
线上排查速查清单
| 现象 | 首先检查 |
|---|---|
启动报 BeanCurrentlyInCreationException |
看堆栈里的创建链;检查构造器注入与 @Async |
| AOP / 事务 / 缓存不生效 | 是否自调用?方法是否 private/final?是否被代理? |
| 启动慢 | 组件扫描范围过大、BPP 过多、循环依赖触发三级缓存代理 |
NoSuchBeanDefinitionException |
包扫描路径、是否有多个实现缺 @Primary、Bean 是否被条件装配排除 |
| 关闭时任务丢失 | 是否配置 server.shutdown=graceful + spring.lifecycle.timeout-per-shutdown-phase |
| 配置注入为 null | 是否用了 static 字段?是否在构造器里就用它?(构造器阶段注入尚未发生) |
11. 一句话总结
Spring IoC 容器在 Java 底层就是一个基于反射与元数据的运行时对象工厂:
BeanDefinition+ConcurrentHashMap------ 解决"运行期才知道要创建什么"Constructor.newInstance()/Field.setAccessible(true)------ 解决"如何动态创建与装配"- 三级缓存 +
ObjectFactory延迟决策 ------ 解决"循环依赖"与"AOP 代理一致性"的双重矛盾,且不为无关 Bean 付出代价 BeanPostProcessor/BeanFactoryPostProcessor------ 解决"如何在生命周期各阶段插手",这是 Spring 生态(MyBatis、Feign、RocketMQ、Redisson...)能无缝集成的根本原因ApplicationContext------ 在BeanFactory之上叠加事件、环境、资源、国际化,并用refresh()驱动整条生命周期
理解了 refresh() → preInstantiateSingletons() → doGetBean() → doCreateBean() 这条主干,再看任何 Spring 生态的集成组件,你都能一眼看穿它在哪个环节做了什么手脚。
附:核心源码位置索引
| 想看什么 | 类和位置 |
|---|---|
| 启动主流程 | org.springframework.context.support.AbstractApplicationContext#refresh |
| BD 注册中心 | org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition |
| 预实例化单例 | DefaultListableBeanFactory#preInstantiateSingletons |
| 获取 Bean 主链路 | AbstractBeanFactory#doGetBean |
| 单例锁与三级缓存 | DefaultSingletonBeanRegistry#getSingleton / addSingleton |
| 单个 Bean 生命周期主干 | AbstractAutowireCapableBeanFactory#doCreateBean |
| 实例化 | AbstractAutowireCapableBeanFactory#createBeanInstance |
| 属性注入 | AbstractAutowireCapableBeanFactory#populateBean |
| 初始化与增强 | AbstractAutowireCapableBeanFactory#initializeBean |
@Autowired 注入 |
AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject |
@PostConstruct 触发 |
CommonAnnotationBeanPostProcessor#postProcessBeforeInitialization |
| AOP 代理生成 | AbstractAutoProxyCreator#postProcessAfterInitialization / getEarlyBeanReference |
| 早期引用工厂 | AbstractAutowireCapableBeanFactory#getEarlyBeanReference |
| 配置类解析 | ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry |