ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Bean生命周期详解:从BeanDefinition到三级缓存

Spring Bean生命周期详解:从BeanDefinition到三级缓存 “面试官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从创建到销毁核心流程我习惯分成十个节点实例化createBeanInstance通过构造器或工厂方法创建对象此时对象刚有内存属性全是默认值。合并BeanDefinition并处理注解元数据applyMergedBeanDefinitionPostProcessors解析Autowired、Resource、PostConstruct等注解的元数据。属性填充populateBean通过AutowiredAnnotationBeanPostProcessor等后置处理器完成依赖注入把Autowired字段、构造器参数、Value配置等赋值进去。提前曝光earlySingletonExposure如果是单例且允许循环引用把ObjectFactory放入三级缓存。Aware回调BeanNameAware、BeanClassLoaderAware、BeanFactoryAware由容器直接调用ApplicationContextAware通过后置处理器回调。BeanPostProcessor前置处理postProcessBeforeInitialization被逐个调用。自定义初始化实现了InitializingBean则调用afterPropertiesSet配置了init-method或Bean(initMethod)则调用对应方法。BeanPostProcessor后置处理postProcessAfterInitialization被逐个调用AOP代理就是在这一步通过AbstractAutoProxyCreator生成的。使用Bean。容器关闭时销毁PreDestroy、DisposableBean.destroy、destroy-method依次执行。注意区分“实例化”和“初始化”。实例化只是new了一个对象属性都还是null真正说一个Bean能干活了要等初始化和属性填充全部完成。这个区分面试时主动说出来就已经比一半候选人强了。1.2 一张表看懂各阶段的触发时机与核心机制把上面对应的机制和常见实现整理成一张表方便记忆阶段触发时机核心机制常见实现BeanDefinition注册容器启动阶段配置解析Configuration、ComponentScan实例化getBean或启动预实例化构造器/工厂方法new、Scope(prototype)时按需创建属性填充实例化之后、初始化之前BeanPostProcessorAutowired、Resource、Value提前曝光单例创建过程中三级缓存循环依赖场景用到Aware回调属性填充后、初始化前后容器回调BeanNameAware、ApplicationContextAware初始化属性填充完成后后置处理器自定义方法PostConstruct、afterPropertiesSet、init-methodAOP代理初始化后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 到 initializeBean3.1 refresh() 里 Bean 是如何被批量创建的要深入理解生命周期绕不开AbstractApplicationContext.refresh()。这是Spring容器的启动入口我们平时说的“容器启动”其实就是执行了refresh()。这个方法里面有几个关键步骤和Bean生命周期直接挂钩invokeBeanFactoryPostProcessors(beanFactory)执行BeanFactoryPostProcessor包括处理Configuration、ComponentScan的ConfigurationClassPostProcessor就是在这步工作的。它是生命周期最靠前的扩展点。registerBeanPostProcessors(beanFactory)注册各种BeanPostProcessor。注意这里只是注册不执行执行要等Bean实例化之后。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 ObjectFactoryObject() { 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 循环依赖到底卡在哪个环节假如有两个BeanA依赖BB依赖AA和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 MapString, Object singletonObjects new ConcurrentHashMap(256); // 二级缓存提前曝光的早期引用半成品Bean MapString, Object earlySingletonObjects new ConcurrentHashMap(16); // 三级缓存可以工厂式生成早期引用的ObjectFactory MapString, ObjectFactory? singletonFactories new HashMap(16);用A和B互相依赖的场景推演一遍创建AgetSingleton(A)未命中创建A实例此时A属性还是空的。调用addSingletonFactory把A的ObjectFactory存入三级缓存。populateBean(A)发现A依赖B于是getSingleton(B)创建B实例B也执行addSingletonFactory把B的ObjectFactory存入三级缓存。populateBean(B)发现B依赖A于是getSingleton(A, true)此时allowEarlyReferencetrue一级缓存没有A二级缓存没有A就从三级缓存找到A的ObjectFactory调用getObject()得到早期引用A把早期引用A放入二级缓存并从三级缓存删除然后返回给BB把A注入成功。B完成属性填充和初始化把完整B放入一级缓存。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.annotationSpring 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 MapString, 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容器的那个人。
返回列表