☰
Spring Bean生命周期详解:从BeanDefinition到三级缓存
2026/10/8 3:30:26 网站建设 项目流程

“面试官:Spring Bean 生命周期详解?”

这道题大概是Spring面试中出现频率最高的前五名。我在几次技术招聘里试过,回答基本分成三类:一类是背了流程但顺序说反;一类能说出实例化、属性填充、初始化、销毁,但说不清Aware回调、BeanPostProcessor、InitializingBean这些扩展点到底卡在哪一步;第三类能把这套东西完整讲下来,但追到循环依赖和三级缓存就明显发虚。

这篇文章就是把这道题从“能答”拆到“能讲漂亮”。我会把Bean生命周期的完整节点、每类扩展点的设计意图、源码里的关键路径,还有面试官最可能顺藤摸瓜追问的三级缓存原理全部串起来。适合正在准备Spring面试的Java开发,也适合想真正理解IoC容器工作方式的后端工程师。你完全可以照这个框架去准备,答完这道题,面试官基本不会再拿“你背过八股”来打发你。

1. 先把 Bean 生命周期的全流程跑一遍

1.1 从 BeanDefinition 到可用实例,到底经历了什么

很多人一上来就背“实例化、属性填充、初始化、销毁”,但漏了最前面的一步:Bean进入容器管理,从BeanDefinition开始。

Spring容器启动时,先解析XML、注解、JavaConfig等配置信息,把它们统一转成BeanDefinition,注册到BeanFactory的注册表里。这一步可理解成“先造好图纸,不急着造产品”。BeanDefinition里面记录着类名、作用域、是否懒加载、初始化方法名、销毁方法名、依赖关系等全部元数据。之后所有Bean的创建,都是拿着这份图纸去做的。

然后才轮到单个Bean的创建。在默认的ApplicationContext场景下,所有非懒加载的单例Bean会在容器启动阶段被批量实例化,也就是refresh()过程中的finishBeanFactoryInitialization这一步。原型Bean则不着急,等每次getBean时才创建。

单个Bean从创建到销毁,核心流程我习惯分成十个节点:

  1. 实例化(createBeanInstance):通过构造器或工厂方法创建对象,此时对象刚有内存,属性全是默认值。
  2. 合并BeanDefinition并处理注解元数据(applyMergedBeanDefinitionPostProcessors):解析@Autowired、@Resource、@PostConstruct等注解的元数据。
  3. 属性填充(populateBean):通过AutowiredAnnotationBeanPostProcessor等后置处理器完成依赖注入,把@Autowired字段、构造器参数、@Value配置等赋值进去。
  4. 提前曝光(earlySingletonExposure):如果是单例且允许循环引用,把ObjectFactory放入三级缓存。
  5. Aware回调:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware由容器直接调用;ApplicationContextAware通过后置处理器回调。
  6. BeanPostProcessor前置处理:postProcessBeforeInitialization被逐个调用。
  7. 自定义初始化:实现了InitializingBean则调用afterPropertiesSet,配置了init-method或@Bean(initMethod)则调用对应方法。
  8. BeanPostProcessor后置处理:postProcessAfterInitialization被逐个调用,AOP代理就是在这一步通过AbstractAutoProxyCreator生成的。
  9. 使用Bean。
  10. 容器关闭时销毁:@PreDestroy、DisposableBean.destroy、destroy-method依次执行。

注意区分“实例化”和“初始化”。实例化只是new了一个对象,属性都还是null;真正说一个Bean能干活了,要等初始化和属性填充全部完成。这个区分面试时主动说出来,就已经比一半候选人强了。

1.2 一张表看懂各阶段的触发时机与核心机制

把上面对应的机制和常见实现整理成一张表,方便记忆:

阶段触发时机核心机制常见实现
BeanDefinition注册容器启动阶段配置解析@Configuration、@ComponentScan
实例化getBean或启动预实例化构造器/工厂方法new、@Scope("prototype")时按需创建
属性填充实例化之后、初始化之前BeanPostProcessor@Autowired、@Resource、@Value
提前曝光单例创建过程中三级缓存循环依赖场景用到
Aware回调属性填充后、初始化前后容器回调BeanNameAware、ApplicationContextAware
初始化属性填充完成后后置处理器+自定义方法@PostConstruct、afterPropertiesSet、init-method
AOP代理初始化后BeanPostProcessor动态代理的子类化包装
使用初始化完成业务调用—
销毁容器关闭回调@PreDestroy、DisposableBean、destroy-method

这张表最大的价值是让你看到Spring的整个生命周期不是一条“顺序调用”的直线,而是围绕着BeanPostProcessor和容器回调把多个机制拼起来的。面试时能画出这张表的逻辑,就说明你不是死记硬背。

1.3 BeanFactory 与 ApplicationContext 的生命周期差异

很多初学者不知道,上面这套完整流程只有在ApplicationContext(包括Spring Boot里的内置容器)里才会全量触发。如果你用的是最底层的BeanFactory,生命周期会缩水不少。

BeanFactory只是最基础的IoC容器,默认懒加载Bean,默认只注册少数内置处理器,很多扩展机制需要手动注册。ApplicationContext在BeanFactory之上做了大量增强:启动时预实例化单例Bean,自动注册AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、ApplicationContextAwareProcessor等一堆内置后置处理器,还会自动执行BeanFactoryPostProcessor。

这个差异你要能一句话说出来:BeanFactory提供的是最小可用的容器能力,ApplicationContext提供了企业级容器的完整生命周期管理。面试里追问“你用的BeanFactory还是ApplicationContext”时,能讲清这个区别,会很加分。

2. 扩展点为什么设置这么多?拆开看设计意图

2.1 两类 Processor 的角色差异

Spring生命周期里最容易搞混的是BeanFactoryPostProcessor和BeanPostProcessor。名字像,职责完全不同。

BeanFactoryPostProcessor作用于BeanDefinition阶段,在所有Bean实例化之前执行,你可以修改BeanDefinition本身的属性、作用域、甚至替换类名。最典型的场景就是修改配置类中被硬编码的参数。举例来说,我曾在不停机的环境下用自定义BeanFactoryPostProcessor把某个第三方依赖Bean的作用域从singleton改成prototype,效果立竿见影。

BeanPostProcessor则作用于Bean实例之后、初始化前后,它改的是对象本身。@Autowired注解的解析、AOP代理的生成、@PostConstruct方法的调用,全部是通过各种BeanPostProcessor实现的。

注意BeanFactoryPostProcessor和BeanPostProcessor的执行时机和对象模型完全不同,一个是改“图纸”,一个是改“产品”。面试官很喜欢用这道题来区分你是真懂还是背书,这个区别自己体会一下。

2.2 Aware 一族:让 Bean 感知容器的时机设计

Aware接口在Spring里是一类标记接口,实现它的Bean可以从容器中获取资源。比如BeanNameAware能拿到当前Bean在容器中的名字,ApplicationContextAware能拿到ApplicationContext对象。它们的出现是为了解决一个问题:Bean明明被容器管理,却不知道容器在哪、自己叫什么。

为什么不直接把ApplicationContext注入所有Bean?因为会强耦合容器,Bean无法脱离Spring容器使用,还会增加内存占用。Aware的设计是“按需索取”,你实现了哪个接口,容器才回调哪个方法。这跟@Autowired自动注入风格不一样,更像是一种显式表达“我需要什么”的声明。

需要注意Aware回调的时机也分两拨。BeanNameAware、BeanClassLoaderAware、BeanFactoryAware是在initializeBean方法里直接调用,比较早;ApplicationContextAware则是通过ApplicationContextAwareProcessor这个BeanPostProcessor在postProcessBeforeInitialization阶段触发,稍微靠后一点。在实际业务代码中,我们很少自己实现ApplicationContextAware,因为Spring已经封装了ApplicationContextHolder之类的工具,但作为面试知识点,你得明白它的触发原理。

2.3 初始化三件套的优先级:@PostConstruct、afterPropertiesSet、init-method

同一个Bean身上,可以同时用三种方式定义初始化逻辑:@PostConstruct注解、实现InitializingBean接口、配置init-method属性。它们的执行顺序基本固定:

@PostConstruct先执行,然后afterPropertiesSet,最后init-method。

原因也很直接:@PostConstruct不是Spring自己的注解,而是JSR-250标准注解,Spring通过CommonAnnotationBeanPostProcessor把它挂在postProcessBeforeInitialization阶段触发,所以在标准初始化方法之前天然先执行。InitializingBean的afterPropertiesSet在Spring源码里直接调用,init-method是最后定义的兜底方案,因此排在最后。

如果配置类里有这样一段代码,可以眼见为实:

@Component public class InitOrderBean implements InitializingBean { @PostConstruct public void postConstruct() { System.out.println("1. @PostConstruct"); } @Override public void afterPropertiesSet() { System.out.println("2. InitializingBean.afterPropertiesSet"); } @BeanInitMethod public void customInit() { System.out.println("3. init-method"); } }

运行后输出顺序就是1、2、3。销毁阶段顺序也类似:@PreDestroy先执行,接着DisposableBean.destroy,最后destroy-method。

这个优先级问题面试出现的频率很高,强烈建议你亲手写一个Bean跑一遍。真跑一次比背十次都牢。

3. 源码脉络:从 refresh 到 initializeBean

3.1 refresh() 里 Bean 是如何被批量创建的

要深入理解生命周期,绕不开AbstractApplicationContext.refresh()。这是Spring容器的启动入口,我们平时说的“容器启动”其实就是执行了refresh()。这个方法里面有几个关键步骤和Bean生命周期直接挂钩:

  1. invokeBeanFactoryPostProcessors(beanFactory):执行BeanFactoryPostProcessor,包括处理@Configuration、@ComponentScan的ConfigurationClassPostProcessor就是在这步工作的。它是生命周期最靠前的扩展点。
  2. registerBeanPostProcessors(beanFactory):注册各种BeanPostProcessor。注意这里只是注册,不执行,执行要等Bean实例化之后。
  3. finishBeanFactoryInitialization(beanFactory):实例化所有非懒加载单例Bean。这一步是单个Bean生命周期的真正起点。
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 准备刷新,设置启动时间、活跃标志等 prepareRefresh(); // 创建或获取 BeanFactory ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 准备BeanFactory,设置类加载器、表达式解析器等 prepareBeanFactory(beanFactory); // ... 模板方法,子类可扩展 // 执行BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory); // 注册BeanPostProcessor registerBeanPostProcessors(beanFactory); // 初始化消息源、事件广播器等 // ... // 实例化所有非懒加载单例Bean finishBeanFactoryInitialization(beanFactory); // 完成刷新,发布事件 finishRefresh(); } }

面试时不要求背源码,能说出refresh()里“先处理BeanFactoryPostProcessor、再注册BeanPostProcessor、最后实例化所有单例Bean”这个顺序,就已经把生命周期前半场的上下文说清楚了。

3.2 doCreateBean:从实例化到提前曝光的完整步骤

单个Bean的创建,核心逻辑在AbstractAutowireCapableBeanFactory.doCreateBean方法里。这个方法每一步都有对应的外部扩展点,可以说是Spring生命周期的“五脏六腑”。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) { BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { // 1. 实例化Bean instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); // 2. 提前曝光:单例且允许循环引用时,把ObjectFactory放入三级缓存 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); } }); } Object exposedObject = bean; try { // 3. 属性填充 populateBean(beanName, mbd, instanceWrapper); // 4. 初始化 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { // ... } return exposedObject; }

注意第2步的提前曝光,这是循环依赖能成立的基础,但也是很多人在面试中讲不出所以然的点。它并没有把Bean本身放到缓存,而是放了一个ObjectFactory,真正被别人引用时才调用getObject()生成“早期引用”。这个设计在后面讲三级缓存时会展开。

3.3 initializeBean 中的四步调用链

initializeBean是单个Bean生命周期的“核心调度室”,它把Aware回调、前置处理、初始化方法、后置处理串在一起:

protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) { // 1. 直接回调BeanNameAware、BeanClassLoaderAware、BeanFactoryAware invokeAwareMethods(beanName, bean); // 2. BeanPostProcessor前置处理(@PostConstruct等方法在这里触发) Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } // 3. 初始化方法:InitializingBean.afterPropertiesSet + init-method try { invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException(...); } // 4. BeanPostProcessor后置处理(AOP代理在这里生成) if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }

这一步最关键的是理解调用顺序:前置处理在初始化方法之前,后置处理在初始化方法之后。AOP代理是在第4步生成的,所以如果你在before阶段拿到的还是原始对象,在after阶段拿到的才可能是代理对象。很多自定义BeanPostProcessor的代码里,就会刻意利用这个时机差。

4. 生命周期里最大的暗礁:循环依赖与三级缓存

4.1 循环依赖到底卡在哪个环节

假如有两个Bean,A依赖B,B依赖A,A和B互相引用对方。Spring能创建出A和B吗?答案取决于注入方式。

如果是构造器注入,Spring会直接抛异常,无法解决;如果是setter注入或字段注入(@Autowired就是在属性填充阶段工作),Spring就能通过三级缓存把循环依赖破掉。

为什么构造器注入不行?因为A的构造器执行时需要B已经存在,可此时B还没创建,Spring根本拿不到B对象,而A本身也还没完成实例化,没法提前暴露任何东西。形象点说,这就好比“你还没出生,就要先见到一个需要你出生之后才能见到的人”。

setter和字段注入就没这个问题,它们发生在populateBean阶段,此时A已经实例化完成,并且已经把ObjectFactory放进了三级缓存。B在创建过程中需要A时,就能通过三级缓存拿回A的早期引用,不管A还没填充完属性,先把引用用起来。

4.2 三级缓存解决的是什么问题

三级缓存存在于DefaultSingletonBeanRegistry里,实际上就是三个Map:

// 一级缓存:最终成品,完整的单例Bean Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二级缓存:提前曝光的早期引用,半成品Bean Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 三级缓存:可以工厂式生成早期引用的ObjectFactory Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

用A和B互相依赖的场景推演一遍:

  1. 创建A:getSingleton(A)未命中,创建A实例,此时A属性还是空的。
  2. 调用addSingletonFactory,把A的ObjectFactory存入三级缓存。
  3. populateBean(A),发现A依赖B,于是getSingleton(B),
  4. 创建B实例,B也执行addSingletonFactory,把B的ObjectFactory存入三级缓存。
  5. populateBean(B),发现B依赖A,于是getSingleton(A, true),此时allowEarlyReference=true,
  6. 一级缓存没有A,二级缓存没有A,就从三级缓存找到A的ObjectFactory,调用getObject()得到早期引用A,
  7. 把早期引用A放入二级缓存,并从三级缓存删除,然后返回给B,B把A注入成功。
  8. B完成属性填充和初始化,把完整B放入一级缓存。
  9. A继续populateBean,拿到B,完成填充和初始化,把完整A放入一级缓存。

这个过程中最关键的是第6步:从三级缓存返回的“早期引用”是一个还没完成初始化的半成品。B拿到这个半成品去填充属性没问题,因为B只是持有引用,并不要求A在那一刻已经完全可用。

对应源码里getSingleton的核心逻辑:

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<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }

注意这个方法里加了一个synchronized锁,因为三级缓存里的singletonFactories不是线程安全的HashMap,必须在并发环境下锁住操作。细节虽小,但面试时能说出来,会显得你真的读过源码。

4.3 为什么是三级缓存,而不是二级或一级

这个问题几乎每逢追问必考。我的回答思路分三层。

第一层,一级缓存不可能:一级缓存存放的必须是完整可用的单例Bean。早期引用是一个属性还没填充完的半成品,放到一级缓存,等于把一个残次品对外开放,别的地方拿到的就是错误数据。

第二层,二级缓存为什么不够:如果只有二级缓存,意味着在无循环依赖的普通场景里,所有单例Bean也必须提前放进二级缓存。那Spring就不得不对每个Bean在提前曝光时都尝试做AOP代理。这个行为会改变AOP的默认触发时机,很可能造成代理重复、代理失效、无辜对象被包装等一堆问题。

第三层,三级缓存的意义在于“延迟决策”:三级缓存里存的不是Bean,而是ObjectFactory。Spring并不知道一个Bean到底需不需要AOP代理,它知道这个决策要等到真正有人引用它时才能做。只有在发生循环依赖、提前引用被触发的那一刻,才调用getEarlyBeanReference去生成可能被代理过的早期引用。这个设计既解决了循环依赖,又保证了AOP代理的正确性。

用一句话概括:三级缓存是为了同时满足“解决循环依赖”和“保持AOP代理时机不被打乱”两个要求,Spring在注册表设计上给出的最优雅方案。

这里还要提一个关键点:如果Bean方法上配置了事务切面、自定义切面等AOP配置,在提前曝光阶段就会通过AbstractAutoProxyCreator生成代理。所以你在开发中看到循环依赖的Bean,拿到的很可能是一个“长得像A”的代理对象,而不是A自己的实例。

5. 面试怎么把这道题讲漂亮:应答框架与追问库

5.1 3到5分钟的应答框架

如果你面试时碰到这道题,我建议按“总分总”的方式组织回答,控制时长在3到5分钟。

先给一句话总览:Bean生命周期可以分成实例化、属性填充、初始化、使用、销毁五个大阶段,Spring通过BeanPostProcessor和各类回调接口把扩展点嵌入其中。

然后快速过节点,不要卡顿。从createBeanInstance开始,一句“属性填充阶段通过populateBean完成依赖注入”,一句“初始化阶段先回调Aware,再走Before后置处理器,然后调InitializingBean,最后走After后置处理器”,再补一句“AOP代理就是在After后置处理器里通过AbstractAutoProxyCreator生成的”,这时候面试官眼神已经不一样了。

接着主动抛出亮点:如果让我补充一个实践点,我会说Spring为单例Bean提供了三级缓存,用来解决循环依赖,核心是ObjectFactory的延迟决策。

最后做个简短收束:整个设计本质上就是模板方法模式,每种扩展点都对应一个特定阶段,给业务留下充分的可定制空间。

5.2 高频追问与参考思路

追问参考思路
BeanPostProcessor和BeanFactoryPostProcessor区别?前者改实例,后者改BeanDefinition;前者执行在实例化之后,后者执行在实例化之前。
三级缓存为什么用ObjectFactory?延迟决策代理的产生时机,保证无循环依赖时AOP照常,有循环依赖时提前生成代理。
@PostConstruct、afterPropertiesSet、init-method执行顺序?依次执行,因为@PostConstruct挂在postProcessBeforeInitialization上。
prototype的Bean会走完整销毁回调吗?不会,Spring不管理prototype的销毁回调,除非自定义DestructionAwareBeanPostProcessor。
构造器注入的循环依赖为什么不行?实例化前需要参数,此时还没有提前曝光对象,三级缓存帮不上忙。
什么时候会触发Bean提前曝光?单例、允许循环引用、isSingletonCurrentlyInCreation为true时。
@Autowired在哪个阶段生效?属性填充阶段,通过AutowiredAnnotationBeanPostProcessor。
销毁回调顺序?@PreDestroy -> DisposableBean.destroy -> destroy-method。

每个追问的回答后面如果能跟上“为什么”或“源码里怎么体现”,这个印象分就拿到手了。

5.3 容易说错的几个细节

我面试时听到过很多“差一点但就是不对”的回答,集中在这几个细节上。

第一,把@PostConstruct说成在afterPropertiesSet之后执行。前面已经说过,它是通过postProcessBeforeInitialization触发的,所以最先执行。

第二,说Bean创建完就立即销毁。销毁只有在ApplicationContext关闭(close方法)时才会触发,正常运行时Bean常驻容器常驻内存,不存在创建完就销毁。

第三,把BeanPostProcessor的两个方法都放在初始化之后。postProcessBeforeInitialization在初始化之前,postProcessAfterInitialization在初始化之后,这个先后关系涉及AOP和Aware回调,说错了很致命。

第四,以为二级缓存只放AOP代理对象。实际上二级缓存放的是所有被提前引用的半成品Bean,只不过如果Bean需要代理,这个引用是代理对象。

第五,说“BeanFactory和ApplicationContext生命周期完全一样”。这个差异前面章节讲了,健忘的话现在翻回去再看一遍。

6. 实战排查:生命周期相关的坑与埋点技巧

6.1 @PostConstruct 不执行,先查这四件事

实际开发中,很多人遇到的最诡异问题就是“为什么我的@PostConstruct方法没跑”。排查顺序建议从外到内。

第一,依赖包是否齐全。Spring 5.3.x之前用的是javax.annotation,Spring 6.x之后迁移到jakarta.annotation,如果包没引或者引错,注解根本不会被识别。

第二,使用XML配置时是否注册了CommonAnnotationBeanPostProcessor。用注解驱动开发时Spring会自动注册,但是老项目里手工配置BeanFactory时,这步可能被漏掉。

第三,方法签名是不是写错了。@PostConstruct要求方法无参数、非静态、无返回值,如果方法带参数或者返回了非void,在CDI规范下该方法是无效的。

第四,Bean是否真的被容器管理。如果通过new的方式创建对象,那Bean生命周期自然不生效,注解也不会有任何反应。这类问题在工具类中特别常见。

6.2 初始化顺序错乱与“不可预测”的列表

Spring对多个Bean之间的初始化顺序默认不保证。如果业务逻辑依赖某个Bean先初始化,必须显式声明依赖关系。

常见的控制方式有四种:@DependsOn注解指定被依赖Bean先初始化;@Order注解控制同一类型组件的加载顺序;实现PriorityOrdered接口;在BeanFactoryPostProcessor中直接排序。最推荐的是@DependsOn,它表达的是“依赖关系”而不仅是“先后顺序”,语义更清晰。

我曾在一个微服务项目里遇到过启动时初始化顺序错乱导致监听器重复注册的问题。排查后确认是多个Runner没有控制先后顺序,后来统一改成@DependsOn加@Order双管齐下,问题解决。顺带提一句:不要在初始化方法里做长耗时或需要外部依赖的操作,这会让Spring Boot的启动时间直线上升,而且很容易掩盖真实依赖顺序的问题。

6.3 让生命周期为我所用:埋点观测的真实案例

如果你想知道每个Bean初始化耗时多少,完全不用上复杂的监控工具,写一个BeanPostProcessor就能搞定。

@Component public class LifecycleLogBeanPostProcessor implements BeanPostProcessor { private final Map<String, Long> startTimeMap = new ConcurrentHashMap<>(); @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { startTimeMap.put(beanName, System.currentTimeMillis()); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Long start = startTimeMap.get(beanName); if (start != null) { long cost = System.currentTimeMillis() - start; System.out.println("Bean [" + beanName + "] 初始化耗时: " + cost + "ms"); startTimeMap.remove(beanName); } return bean; } }

这段代码会把所有Bean的初始化耗时输出到控制台。排查启动慢问题时,它就是一把快速定位的尺子。

还有一类场景在生命周期里特别容易踩雷:在DisposableBean的destroy方法里访问其他Bean。销毁阶段不是按创建的反序执行的,Spring只保证同一个Bean内部回调顺序,不保证Bean之间的销毁顺序。如果你的销毁逻辑依赖另一个Bean,很可能已经拿到一个正在销毁中的对象。我的建议是销毁逻辑里只做最低限度的清理工作,不要把业务补偿逻辑放在这里。


按我的经验来说,这道题很多人答不好,不是不知道流程,而是不理解“为什么Spring要设置这么多扩展点”。你准备的时候别死背顺序,而是把Spring想解决的问题——对象创建、依赖装配、扩展定制、代理增强、销毁清理——按问题去记,面试时从问题讲机制,给面试官的感觉就完全是另一个层次。

最后再分享一个小技巧。准备这道题时,自己开一个Spring Boot工程,定义两个互相依赖的Bean,分别实现InitializingBean、@PostConstruct、各写Aware接口,再加一个自定义BeanPostProcessor,全项目打上日志。跑一遍,你会看到所有回调的触发顺序和对象状态。亲手观察一遍,比背十遍八股都牢。等你能解释为什么需要三级缓存的时候,你已经不只是会背八股的人,而是真正理解IoC容器的那个人。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询