
干这行这么多年Spring Bean生命周期是被问得最多、也最容易讲混的一道题。刚工作那会儿背过八股后来手写迷你Spring、排查线上循环依赖问题、给同事讲解三级缓存原理才逐渐把这些阶段真正串成一条线。这篇就把我理解的完整生命周期、源码层面的关键节点以及面试和实战里常遇到的坑一并讲清楚。Spring Bean生命周期不是说new一个对象那么简单它涵盖了从配置解析、实例化、属性填充、初始化到最终销毁的全过程。理解它你才能真正看懂Spring容器做了什么、BeanPostProcessor在哪一步介入、循环依赖为什么能解决、AOP代理又是在哪个环节生效的。无论你是刚入门的Java开发者还是准备高级岗位面试的选手这部分内容都值得花时间彻底吃透。1. 生命周期全景图谱一道面试题背后的大厦1.1 为什么Bean生命周期是理解Spring的钥匙很多同学学Spring第一件事就是写一个Controller配一个Service然后注解一加就跑起来了。但一旦遇到问题就抓瞎为什么Autowired能注入成功为什么AOP切面有时候生效有时候不生效为什么循环依赖在Spring Boot里默认能跑起来这些问题的答案全都在Bean生命周期里。我习惯把Spring容器比作一家餐厅的后厨。你只需要点菜声明一个Bean后面的事情——买菜解析配置、洗菜切菜实例化、配菜装盘属性填充、上灶烹饪初始化、营业结束打烊销毁——全部由后厨流水线完成。后厨有一个个小工BeanPostProcessor在上菜前后可以偷偷给菜品加料增强逻辑、生成代理。搞懂这条流水线你就掌握了Spring框架里90%的“魔法”。尤其是后厨里小工BeanPostProcessor干活的位置决定了AOP、事务、异步注解这些东西能不能生效。1.2 全景流程从配置到容器销毁的八个阶段先看完整路径这八个阶段是我自己梳理的背下来后面就好理解了。BeanDefinition解析Spring读取XML配置、注解或Java Config生成BeanDefinition元数据。实例化Instantiation通过构造器反射创建原始对象此时对象的所有属性都是默认值。属性填充Populate根据依赖注入配置填充基本类型属性、对象引用Autowired、Resource、集合等。Aware回调如果Bean实现了BeanNameAware、BeanFactoryAware、ApplicationContextAware等接口在此阶段回调把容器信息告诉Bean。BeanPostProcessor前置处理postProcessBeforeInitialization在初始化前的钩子Spring自身的ApplicationContextAwareProcessor、AutowiredAnnotationBeanPostProcessor都在这里工作。初始化Initialization依次执行PostConstruct方法、InitializingBean接口的afterPropertiesSet方法、自定义init-method。BeanPostProcessor后置处理postProcessAfterInitialization初始化后的钩子AOP创建代理对象的关键位置就在这里。使用与销毁Destruction容器关闭时依次执行PreDestroy方法、DisposableBean接口的destroy方法、自定义destroy-method。注意步骤7是个分水岭。很多同学理解的“Bean”其实是代理对象真实Bean可能在步骤7就被藏起来了。下面给一个完整速查表接口/注解和对应阶段一目了然机制执行阶段典型用途构造器实例化创建原始对象建议注入必需依赖Autowired / setter属性填充依赖注入BeanNameAware阶段4获取BeanNameBeanFactoryAware阶段4获取BeanFactoryApplicationContextAware阶段4获取ApplicationContextpostProcessBeforeInitialization阶段5修改Bean属性、包装BeanPostConstruct阶段6初始化资源、校验配置afterPropertiesSet阶段6初始化逻辑Spring原生接口init-method阶段6XML时代的初始化方法postProcessAfterInitialization阶段7AOP代理、生成代理BeanPreDestroy阶段8释放资源、关闭连接destroy阶段8销毁逻辑DisposableBeandestroy-method阶段8自定义销毁方法1.3 为什么单例Bean和原型Bean生命周期不一样Bean作用域不同生命周期管理的深度也不同。单例singletonBean的完整生命周期由Spring容器管理从实例化到销毁全流程都走完容器关闭时会触发销毁回调。这也是最常见的Bean形态。原型prototypeBean就要特别注意创建后容器会交付给调用方容器只负责“生”不负责“养”。初始化回调PostConstruct等会执行但销毁回调PreDestroy等根本不会自动执行。什么时候释放资源完全取决于使用方自己。我在项目里吃过这个亏把某个耗时任务Bean设为prototype里面用到了线程池结果容器销毁后线程池一直没关闭导致应用多次重启后线程泄露。后来改成手动调用销毁方法才解决。这个问题在面试里也经常被问记住一句话prototype作用域下Spring只管生不管死。2. 核心阶段源码级拆解每个节点背后发生了什么2.1 实例化Spring怎么把一个类变成对象实例化是整个生命周期的起点。Spring默认通过无参构造器或指定构造器来创建对象底层是反射调用。正常路径下AbstractAutowireCapableBeanFactory的createBeanInstance方法会做三件事检查BeanDefinition是否指定了Supplier或工厂方法通过构造器解析ConstructorResolver匹配最合适的构造器反射实例化这里面有个重要参数Autowired标注在有参构造器上时Spring会自动选择该构造器进行依赖注入。Spring框架5.x之后如果Bean只有一个构造器即使不标注Autowired也会被自动使用。我见过老项目里一个人写了三四个构造器结果Spring不知道该选哪个直接报错。解决办法很明确要么保留无参构造器要么明确标注一个主构造器。实例化阶段有个容易被忽略的细节实例化返回的原始对象此时还是“裸”的属性全是默认值。如果你想在实例化后、属性填充前做点事情可以实现InstantiationAwareBeanPostProcessor接口在postProcessBeforeInstantiation方法中返回自定义实例。Spring自身用这个扩展点实现了AOP的提前代理AbstractAutoProxyCreator就在这一步检查是否需要为当前Bean创建代理这也是三级缓存能解决循环依赖的关键前置条件。2.2 属性填充与Aware回调Bean如何“组装”起来实例化之后进入populateBean阶段这是依赖注入真正发生的地方。Spring会先处理InstantiationAwareBeanPostProcessor的postProcessProperties方法AutowiredAnnotationBeanPostProcessor利用这个方法扫描Autowired和Value注解然后逐个解析依赖并注入。所以在面试里我说过一句话Autowired的本质是一个BeanPostProcessor在属性填充阶段干的活推而广之Resource是CommonAnnotationBeanPostProcessor干的活两者底层套路一致都是“后置处理器扫描注解、解析依赖、强制注入”。依赖注入在属性填充阶段完成后Spring进入Aware回调阶段。这里多个Aware接口的调用顺序是固定的BeanNameAware回调setBeanName传入Bean的id/name。BeanClassLoaderAware回调setBeanClassLoader传入加载该Bean的ClassLoader。BeanFactoryAware回调setBeanFactory传入BeanFactory实例用于后续手动获取其他Bean。ApplicationContextAware这个不是由BeanFactory直接回调的而是通过ApplicationContextAwareProcessor这个BeanPostProcessor在postProcessBeforeInitialization阶段回调setApplicationContext。每次面试我都建议候选人把这些Aware接口的触发时机背熟尤其是ApplicationContextAware的触发位置。它不在属性填充阶段而在初始化前的前置处理里。这解释了为什么在setter注入时拿不到ApplicationContext但在PostConstruct里可以。2.3 BeanPostProcessorSpring最强大的扩展点如果说Bean生命周期是一条流水线BeanPostProcessor就是可以在流水线上自由布置的检查站。接口定义两个方法public interface BeanPostProcessor { default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }这两个方法分别作用于初始化方法PostConstruct、afterPropertiesSet等的前后。注意返回值你可以返回包装后的代理对象也可以返回原对象甚至可以偷天换日返回一个完全不同的对象——只要类型兼容。我自己写过一个需求给订单服务中所有接口方法打日志但不想侵入业务代码。最初方案是用AOP后来发现某个遗留Bean是XML配置的老接口直接实现BeanPostProcessor在postProcessAfterInitialization阶段判断Bean类型然后用JDK动态代理包一层写日志、记录耗时、做降级开关。这么干完全绕开了AOP对类加载顺序、切面配置的依赖效果还特别干净。Spring内部大量核心功能都是这样钩上来的AutowiredAnnotationBeanPostProcessor处理AutowiredCommonAnnotationBeanPostProcessor处理Resource、PostConstruct、PreDestroyAbstractAutoProxyCreator创建AOP代理支撑Transactional、AsyncApplicationContextAwareProcessor处理ApplicationContextAware回调所以“如何向Spring容器中动态注入Bean”“如何给Bean动态生成代理”本质都是自定义BeanPostProcessor。2.4 初始化三兄弟PostConstruct、InitializingBean、init-method初始化阶段同一时间点有三个执行入口顺序是固定不变的先执行PostConstruct注解方法再执行InitializingBean接口的afterPropertiesSet()最后执行XML配置或Bean(initMethod ...)指定的init-method用一段验证代码Component public class LifeCycleBean { public LifeCycleBean() { System.out.println(1. 构造方法); } PostConstruct public void postConstruct() { System.out.println(2. PostConstruct); } Override public void afterPropertiesSet() { System.out.println(3. InitializingBean.afterPropertiesSet); } public void customInit() { System.out.println(4. init-method); } }配合Bean(initMethod customInit)注册运行后输出顺序固定为1234。这个顺序在源码里由initializeBean方法保证protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) { // 先执行Aware回调 invokeAwareMethods(beanName, bean); // 前置处理PostConstruct所在阶段 Object wrappedBean applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); // 初始化afterPropertiesSet init-method invokeInitMethods(beanName, wrappedBean, mbd); // 后置处理AOP代理 wrappedBean applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); return wrappedBean; }看到源码就明白为什么PostConstruct总是先执行它是靠CommonAnnotationBeanPostProcessor在postProcessBeforeInitialization里触发的天然排在afterPropertiesSet之前。实践建议项目里统一用一种初始化方式。我主导的团队规范里约定业务代码用PostConstruct框架性、组件性的代码用InitializingBean。init-method只用于XML配置遗留系统新代码不写能在代码里显式看到生命周期钩子的地方尽量不用XML魔法。2.5 销毁阶段资源释放的最后一道门容器关闭时Spring会遍历所有单例Bean找到SmartInitializingSingleton和DisposableBean进行处理执行顺序同样固定先执行PreDestroy标注的方法再执行DisposableBean的destroy()方法最后执行配置的destroy-methodComponent public class LifeCycleBean { PreDestroy public void preDestroy() { System.out.println(1. PreDestroy); } Override public void destroy() { System.out.println(2. DisposableBean.destroy); } public void customDestroy() { System.out.println(3. destroy-method); } }日常开发常见的一个错把数据库连接池、线程池放在PreDestroy里关闭看似合理但在某些优雅停机场景下Spring Cloud的注册中心摘除和Bean销毁的先后顺序会引发短时间调用失败。我们的做法是在PreDestroy里先切流量再关闭连接池或者实现SmartLifecycle接口通过getPhase控制停机阶段的优先级。销毁阶段还有一个容器的行为值得注意单例Bean默认在容器关闭时统一销毁但prototypeBean不会。有人问那prototype Bean的资源怎么释放两个办法一是自己维护引用列表在容器关闭钩子里手动清理二是用一个专门的“消费者”单例Bean管理原型Bean的销毁。没有第三种自动方案。3. 三级缓存生命周期里最容易被神话的原理3.1 为什么需要三级缓存Spring的循环依赖指的是A依赖B、B依赖A。在属性填充阶段A需要BB需要一个初始化的A但A还没初始化完——正常的生命周期走不通。我先把结论说清楚三级缓存解决的是单例Bean在属性填充阶段发生循环依赖的问题它不解决构造器注入的循环依赖也不解决prototype Bean的循环依赖。三级缓存是三个Map名字和用途很有讲究/** 一级缓存完整的成品Bean */ private final MapString, Object singletonObjects new ConcurrentHashMap(256); /** 二级缓存早期暴露的原始Bean尚未完成初始化 */ private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16); /** 三级缓存ObjectFactory用于生成早期引用 */ private final MapString, ObjectFactory? singletonFactories new HashMap(16);缓存命名的设计其实透露了生命周期阶段实例化之后Bean还没填充属性Spring会把一个ObjectFactory放进三级缓存发生循环依赖时通过三级缓存的工厂方法提前拿到一个“早期引用”放入二级缓存初始化完成放入一级缓存同时清理二、三级缓存3.2 三级缓存解决循环依赖的完整时序假设A和B互相依赖过程大致这样Spring开始创建A反射实例化A得到原始对象a。提前把a包装成ObjectFactory放入三级缓存。开始给a填充属性发现依赖B。去容器里找B发现B没有创建于是开始创建B。Spring实例化B把B的ObjectFactory放入三级缓存。给B填充属性发现依赖A。查找A三级缓存里有A的ObjectFactory调用getEarlyBeanReference得到一个“早期引用”。此时如果A需要AOP这个早期引用可能就是代理对象但A的初始化方法PostConstruct等还没执行。B拿到早期A顺利完成属性填充初始化完成B被放入一级缓存。回到A的创建流程A拿到已经创建好的B继续填充属性完成初始化。A最终完成后放入一级缓存。整个过程里A并不是“完整生命周期走完”后才被B引用而是处于“半成品”状态就被分享出去了。这正是三级缓存的本质把Bean的可用性可被引用和完整性已完成初始化解耦。3.3 从缓存看生命周期的两个延伸结论第一个结论为什么构造器注入无法解决循环依赖。构造器注入要求A在实例化时就把B准备好但B实例化时又需要A的构造器参数此时A连“半成品”状态都不存在三级缓存里根本找不到可以提前暴露的对象死锁直接报错。我会在项目规范里明确核心Bean禁止构造器循环依赖一旦出现就要重构。第二个结论AOP代理在三级缓存阶段就参与进来了。第三步提到getEarlyBeanReference这个方法会检查Bean是否需要增强是否有Advisor匹配需要就提前创建代理对象。结合BeanPostProcessor第7步postProcessAfterInitialization也会创建代理你可能会问会不会重复代理不会Spring有earlyProxyReferences机制通过一个Map记录哪些Bean已经被提前代理后置阶段就跳过。这解释了面试高频问题循环依赖情况下为什么注入到B里的A已经是代理对象因为A在三级缓存阶段通过ObjectFactory提前完成了代理创建B拿到手里就是增强后的对象。4. 手写一个迷你Spring自己动手理解生命周期全过程4.1 核心设计思路背再多理论不如动手写一遍。我曾在技术分享时带着团队用100多行代码实现过一个只支持Component扫描和Autowired的迷你Spring核心就是模拟Bean生命周期的各个阶段。这段经验让我真正理解了Spring设计者的意图。设计方向扫描指定包下所有类解析带Component注解的类注册BeanDefinition按生命周期顺序处理实例化、属性填充、Aware回调、初始化、后置处理维护一个极简三级缓存演示循环依赖解决定义一个BeanPostProcessor接口验证它在生命周期中的位置这个迷你框架虽然简陋但跑起来后能清晰打印出每个Bean在每个阶段的调用日志。4.2 核心代码实现自定义注解Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) public interface MiniComponent { } Target(ElementType.FIELD) Retention(RetentionPolicy.RUNTIME) public interface MiniAutowired { } Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface MiniPostConstruct { }生命周期核心容器public class MiniApplicationContext { private final MapString, Object singletonObjects new ConcurrentHashMap(); private final MapString, Object earlySingletonObjects new ConcurrentHashMap(); private final MapString, ObjectFactory? singletonFactories new ConcurrentHashMap(); private final ListBeanPostProcessor postProcessors new ArrayList(); public MiniApplicationContext(Class? configClass) { // 1. 扫描包路径注册BeanDefinition scan(configClass.getPackageName()); // 2. 预实例化所有单例Bean模拟Spring的preInstantiateSingletons singletonNames.forEach(this::getBean); // 3. 注册容器关闭钩子 } public Object getBean(String beanName) { Object bean singletonObjects.get(beanName); if (bean ! null) { return bean; } // 二级缓存命中早期引用 bean earlySingletonObjects.get(beanName); if (bean ! null) { return bean; } // 三级缓存生成早期引用 ObjectFactory? factory singletonFactories.get(beanName); if (factory ! null) { bean factory.getObject(); earlySingletonObjects.put(beanName, bean); singletonFactories.remove(beanName); return bean; } return doCreateBean(beanName); } private Object doCreateBean(String beanName) { // 阶段1: 实例化 Object bean instantiate(beanName); // 阶段2: 提前暴露到三级缓存解决循环依赖 singletonFactories.put(beanName, () - { // 这里会调用BeanPostProcessor实现AOP代理逻辑 return applyPostProcessorsBeforeInit(bean, beanName); }); // 阶段3: 属性填充模拟Autowired populateBean(bean, beanName); // 阶段4: 初始化PostConstruct invokeInitMethods(bean, beanName); // 阶段5: 完整Bean放入一级缓存 singletonObjects.put(beanName, bean); earlySingletonObjects.remove(beanName); singletonFactories.remove(beanName); return bean; } private void populateBean(Object bean, String beanName) { for (Field field : bean.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(MiniAutowired.class)) { field.setAccessible(true); String dependName field.getName(); Object dependBean getBean(dependName); // 触发循环依赖解析 field.set(bean, dependBean); } } } private void invokeInitMethods(Object bean, String beanName) { for (Method method : bean.getClass().getDeclaredMethods()) { if (method.isAnnotationPresent(MiniPostConstruct.class)) { method.invoke(bean); } } } }这段代码虽然简单但套上了BeanPostProcessor之后就能完整模拟Spring最核心的两个能力AOP代理介入时机、循环依赖解决。4.3 用迷你Spring验证循环依赖定义两个互相依赖的类MiniComponent public class ServiceA { MiniAutowired private ServiceB serviceB; MiniPostConstruct public void init() { System.out.println(ServiceA init...); } } MiniComponent public class ServiceB { MiniAutowired private ServiceA serviceA; MiniPostConstruct public void init() { System.out.println(ServiceB init...); } }用上面的容器跑一下你会发现打印顺序符合三级缓存的演进A实例化、A放入三级缓存、B实例化、B注入A拿到早期引用、B初始化、B入一级缓存、A继续初始化、A入一级缓存。手写过一次你会彻底明白Bean生命周期不是一个线性“背下来”的东西而是一套为了支撑依赖注入、AOP、循环依赖等能力设计的有序流程。面试时被问到“Bean生命周期和三级缓存的关系”你就能从源码设计意图来回答而不是背书。5. 高频坑点与排查实录实战中的生命周期陷阱5.1 PostConstruct不生效的几种场景常见问题一方法不是public。Spring的CommonAnnotationBeanPostProcessor会解析标注了PostConstruct的方法但要求方法不能是static且不能带参数。如果你的方法写成private void init()有些版本不会执行或者反射调用时异常。常见问题二Bean不是由Spring管理的。PostConstruct只会对容器管理的Bean生效如果自己new了一个对象或者通过Bean方法手动返回new出来的对象没有交给Spring注册注解自然不生效。常见问题三类被CGLIB代理了。Spring的Configuration类默认用CGLIB增强内部方法调用如果绕过代理初始化逻辑可能执行两次或者根本不按预期执行。排查手段很简单在方法里打日志看执行了几次。排查这类问题我有个习惯先确认Bean的创建路径再确认BeanPostProcessor是否注册。启动参数加上-DdebugtrueBean的注册和增强过程会输出到控制台比自己猜快得多。5.2 循环依赖下AOP失效的真相循环依赖场景里A被B提前引用时如果A需要AOP增强那么代理对象在三级缓存阶段就会生成。这个代理对象是依据当时已经匹配的Advisor生成的。一般情况下没问题但有一种极端情况某个Advisor依赖BBean的属性Advisor本身也参与循环依赖此时Advisor可能还没完全初始化导致A没有被代理或者代理不完整。我在实际项目里遇到过一次一个事务切面依赖了一个配置Bean配置Bean和业务Bean形成循环依赖最终结果是一条事务注解始终不生效。定位用了整整半天后来用Lazy注解打破循环才恢复正常。这也是为什么Spring官方文档建议如果可以用Lazy避免循环依赖就别依赖三级缓存机制。5.3 PreDestroy不执行优雅停机问题Spring Boot应用在kill -9下不会执行销毁钩子这一点必须要清楚。正常停机kill默认信号或调用ApplicationContext.close()才能触发PreDestroy。如果你用System.exit(0)退出Spring Boot应用默认情况下Bean销毁流程也是不走的。正确做法是调用SpringApplication.exit(applicationContext, () - 0)。Kubernetes部署场景下Pod被驱逐时发的是SIGTERMSpring Boot 2.3默认会等待优雅停机需要配置spring.lifecycle.timeout-per-shutdown-phase这时候Bean的PreDestroy才会真正执行。排查走查清单我一般看三处进程收到的信号是SIGKILL还是SIGTERM是否配置了spring.main.register-shutdown-hooktrue默认true但确认一下没坏处销毁方法内部是否抛异常打断了后续销毁流程5.4 面试应答的思路从背诵到理解以我面过上百人的经验Bean生命周期这道题的优秀回答不是把八个阶段背全而是能解释“为什么”。我建议的应答结构先讲完整流程实例化、属性填充、Aware回调、BeanPostProcessor前置处理、初始化三入口PostConstruct - afterPropertiesSet - init-method、BeanPostProcessor后置处理、销毁三出口PreDestroy - destroy - destroy-method。再讲扩展点指出BeanPostProcessor是Spring最核心的扩展口AOP、事务、Autowired全是在这个口子上实现的。主动提三级缓存说明循环依赖解决的本质是“实例化和初始化分离”早期引用通过ObjectFactory从三级缓存暴露给依赖方AOP代理在这个环节提前介入。提一个自己踩过的坑比如prototype Bean不执行销毁回调、PostConstruct在代理场景下重复执行这些真实经验会让面试官知道你确实在生产环境里排查过问题。这样的回答完全基于生命周期理解比单纯背八股有说服力得多。最后分享一点个人体会我见过太多人把Bean生命周期当作面试题来背背完就忘。这很正常因为生命周期不是孤立概念它和依赖注入、AOP、循环依赖是一张网。真正把它融会贯通的方法就一个动手写一个迷你容器或者出问题时顺着源码把调用栈走一遍。我最初手写三级缓存的时候代码又丑又乱但写完后再看Spring源码很多原来“记不住”的设计细节一下子就通了。如果你打算深入这块我建议从DefaultListableBeanFactory和AbstractAutowireCapableBeanFactory两个类入手配合断点观察doCreateBean方法的执行过程。启动一个最简单的Spring Boot工程在doCreateBean里打断点逐步看Bean是怎么从原始对象变成最终成品的整个过程的记忆会比任何八股都深刻。