我做技术面试官这么多年Bean的生命周期是每次面试必问的问题80%的开发者都只能答个大概比如实例化、初始化、销毁再往深了问比如各个扩展点的触发时机、循环依赖的解决方案90%的人都答不上来。其实Bean的生命周期是Spring的核心理解了这个你才能看懂Spring的源码才能用好Spring的各种扩展点。今天我就从源码角度把Bean的生命周期讲透所有面试考点都给你提炼好了看完再也不怕面试问这个。一、Bean生命周期核心流程总览Bean的生命周期看起来复杂其实核心就是四个大阶段实例化→属性注入→初始化→销毁每个阶段中间穿插了各种扩展点的执行。我们先看总流程图实例化Bean → 属性注入 → 执行Aware接口方法 → BeanPostProcessor前置处理 → 初始化 → BeanPostProcessor后置处理 → Bean可以使用 → 销毁Bean的创建核心源码在AbstractAutowireCapableBeanFactory的doCreateBean方法里我们直接看源码// 简化后的doCreateBean方法protectedObjectdoCreateBean(StringbeanName,RootBeanDefinitionmbd,NullableObject[]args)throwsBeanCreationException{BeanWrapperinstanceWrappernull;if(instanceWrappernull){// 1. 实例化Bean调用构造方法创建Bean对象instanceWrappercreateBeanInstance(beanName,mbd,args);}ObjectbeaninstanceWrapper.getWrappedInstance();Class?beanTypeinstanceWrapper.getWrappedClass();// 2. 属性注入给Bean的属性赋值populateBean(beanName,mbd,instanceWrapper);// 3. 初始化Bean执行初始化逻辑ObjectexposedObjectinitializeBean(beanName,exposedObject,mbd);// 4. 注册销毁方法try{registerDisposableBeanIfNecessary(beanName,bean,mbd);}catch(BeanDefinitionValidationExceptionex){thrownewBeanCreationException(mbd.getResourceDescription(),beanName,Invalid destruction signature,ex);}returnexposedObject;}所有核心逻辑都在这四个方法里我们一个个拆解。二、生命周期各阶段详细拆解2.1 阶段1实例化createBeanInstance这个阶段就是调用Bean的构造方法创建一个空的对象还没有给属性赋值。Spring会根据Bean的配置选择合适的构造方法如果有默认无参构造方法优先调用无参构造。如果只有一个有参构造方法就调用这个有参构造自动注入构造方法的参数。如果有多个有参构造方法需要用Autowired注解指定用哪个。踩坑经验如果Bean只有有参构造方法而且参数的Bean不存在就会报错所以建议每个Bean都提供一个无参构造方法避免出现实例化失败的问题。2.2 阶段2属性注入populateBean实例化完成后就会给Bean的属性赋值比如Autowired、Value、Resource注解注入的属性都是在这个阶段赋值的。Spring会先解析Bean的所有属性然后根据属性的类型或者名称从容器里找到对应的Bean赋值到当前Bean的属性上。2.3 阶段3初始化initializeBean这个阶段是Bean初始化的核心我们看initializeBean的源码protectedObjectinitializeBean(StringbeanName,Objectbean,NullableRootBeanDefinitionmbd){// 执行Aware接口的方法if(System.getSecurityManager()!null){AccessController.doPrivileged((PrivilegedActionObject)()-{invokeAwareMethods(beanName,bean);returnnull;},getAccessControlContext());}else{invokeAwareMethods(beanName,bean);}ObjectwrappedBeanbean;// 执行BeanPostProcessor的前置处理方法if(mbdnull||!mbd.isSynthetic()){wrappedBeanapplyBeanPostProcessorsBeforeInitialization(wrappedBean,beanName);}// 执行初始化方法try{invokeInitMethods(beanName,wrappedBean,mbd);}catch(Throwableex){thrownewBeanCreationException((mbd!null?mbd.getResourceDescription():null),beanName,Invocation of init method failed,ex);}// 执行BeanPostProcessor的后置处理方法if(mbdnull||!mbd.isSynthetic()){wrappedBeanapplyBeanPostProcessorsAfterInitialization(wrappedBean,beanName);}returnwrappedBean;}这个方法里分三步第一步执行Aware接口方法如果Bean实现了Aware接口就会执行对应的方法常见的Aware接口有BeanNameAware注入当前Bean的名称BeanFactoryAware注入BeanFactory实例ApplicationContextAware注入ApplicationContext实例EnvironmentAware注入Environment实例用来获取配置第二步BeanPostProcessor前置处理执行所有BeanPostProcessor的postProcessBeforeInitialization方法在初始化之前对Bean做修改比如我们之前讲的参数脱敏就是在这里做代理的。第三步执行初始化方法初始化方法的执行顺序是执行PostConstruct注解标注的方法执行InitializingBean接口的afterPropertiesSet方法执行自定义的init-method方法在Bean注解里指定initMethod参数第四步BeanPostProcessor后置处理执行所有BeanPostProcessor的postProcessAfterInitialization方法在初始化之后对Bean做增强比如AOP的动态代理就是在这里生成的。到这里Bean的初始化就完成了这个Bean就可以正常使用了。2.4 阶段4销毁当Spring容器关闭的时候会执行Bean的销毁方法执行顺序是执行PreDestroy注解标注的方法执行DisposableBean接口的destroy方法执行自定义的destroy-method方法在Bean注解里指定destroyMethod参数三、循环依赖解决方案三级缓存原理循环依赖是面试必问的考点比如A依赖BB依赖ASpring是怎么解决这个问题的答案就是三级缓存。3.1 什么是三级缓存Spring的三级缓存是三个Map// 一级缓存保存已经创建完成的BeanprivatefinalMapString,ObjectsingletonObjectsnewConcurrentHashMap(256);// 二级缓存保存提前暴露的半成品Bean已经实例化还没初始化privatefinalMapString,ObjectearlySingletonObjectsnewHashMap(16);// 三级缓存保存Bean的工厂对象用来生成代理对象privatefinalMapString,ObjectFactory?singletonFactoriesnewHashMap(16);3.2 循环依赖解决流程我们以A依赖BB依赖A为例看解决流程开始创建A实例化A还没属性注入把A的工厂对象放到三级缓存里。给A注入属性B发现B还没创建开始创建B。实例化B还没属性注入把B的工厂对象放到三级缓存里。给B注入属性A从三级缓存里拿到A的工厂对象生成A的提前暴露对象放到二级缓存里删除三级缓存里的A的工厂对象。B注入A成功继续执行B的初始化B创建完成放到一级缓存里。回到A的创建流程注入B成功继续执行A的初始化A创建完成放到一级缓存里删除二级缓存里的A。循环依赖解决完成。3.3 常见面试问题为什么要用三级缓存二级缓存能不能解决循环依赖答如果没有AOP的话二级缓存就能解决循环依赖但如果有AOP的话需要生成代理对象三级缓存的ObjectFactory就是用来生成代理对象的提前暴露的对象必须是代理对象而不是原始对象所以需要三级缓存。构造方法注入的循环依赖为什么解决不了答因为构造方法注入的时候实例化A的时候就需要B的实例这时候A还没实例化完成没法放到三级缓存里所以解决不了。可以用Lazy注解延迟加载B实例化A的时候先注入B的代理对象等真正用到B的时候再创建B的实例。原型Bean的循环依赖为什么解决不了答原型Bean不会放到缓存里每次创建都是新的对象所以没法提前暴露对象解决不了循环依赖。四、各扩展点在生命周期中的触发时机我们之前讲过的5个高级扩展点在生命周期中的触发顺序是BeanFactoryPostProcessor在所有Bean实例化之前执行用来修改Bean定义。ApplicationContextInitializer在上下文刷新之前执行比BeanFactoryPostProcessor还早。BeanPostProcessor.postProcessBeforeInitialization在Bean初始化之前执行。InitializingBean.afterPropertiesSet在Bean初始化阶段执行。BeanPostProcessor.postProcessAfterInitialization在Bean初始化之后执行。ApplicationListener监听对应的事件在事件发布的时候执行。SpringBootExceptionReporter启动失败的时候执行。我之前面试的时候经常问这个问题很多人都搞不清顺序其实只要记住越宏观的扩展点执行越早Bean级别的扩展点在Bean创建的时候执行。五、面试高频考点提炼简述Spring Bean的生命周期PostConstruct、afterPropertiesSet、init-method的执行顺序Spring怎么解决循环依赖三级缓存的作用是什么构造方法注入的循环依赖怎么解决BeanPostProcessor的执行时机有什么作用Aware接口有哪些作用是什么PreDestroy、destroy、destroy-method的执行顺序原型Bean的循环依赖能不能解决为什么六、踩坑经验总结PostConstruct方法不要做耗时操作PostConstruct是同步执行的会阻塞Bean的创建导致启动慢耗时的操作要放到ApplicationRunner或者异步线程里执行。不要在初始化方法里依赖还没创建的Bean如果你依赖的Bean还没创建会报错可以用DependsOn注解指定依赖的Bean的创建顺序。循环依赖尽量避免虽然Spring能解决setter注入的循环依赖但循环依赖说明你的代码设计有问题尽量优化代码结构避免循环依赖。销毁方法要释放资源比如数据库连接、线程池、文件流这些资源要在销毁方法里释放避免资源泄漏。总结Bean的生命周期是Spring的核心理解了这个你才能看懂Spring的源码才能用好Spring的各种扩展点。大家不用死记硬背多看几遍doCreateBean的源码自己写几个测试例子打断点调试一下很快就能理解了。面试的时候按照这个流程答面试官肯定会对你刮目相看。
大厂面试必问:Spring Boot Bean的生命周期,从源码角度全解析
我做技术面试官这么多年Bean的生命周期是每次面试必问的问题80%的开发者都只能答个大概比如实例化、初始化、销毁再往深了问比如各个扩展点的触发时机、循环依赖的解决方案90%的人都答不上来。其实Bean的生命周期是Spring的核心理解了这个你才能看懂Spring的源码才能用好Spring的各种扩展点。今天我就从源码角度把Bean的生命周期讲透所有面试考点都给你提炼好了看完再也不怕面试问这个。一、Bean生命周期核心流程总览Bean的生命周期看起来复杂其实核心就是四个大阶段实例化→属性注入→初始化→销毁每个阶段中间穿插了各种扩展点的执行。我们先看总流程图实例化Bean → 属性注入 → 执行Aware接口方法 → BeanPostProcessor前置处理 → 初始化 → BeanPostProcessor后置处理 → Bean可以使用 → 销毁Bean的创建核心源码在AbstractAutowireCapableBeanFactory的doCreateBean方法里我们直接看源码// 简化后的doCreateBean方法protectedObjectdoCreateBean(StringbeanName,RootBeanDefinitionmbd,NullableObject[]args)throwsBeanCreationException{BeanWrapperinstanceWrappernull;if(instanceWrappernull){// 1. 实例化Bean调用构造方法创建Bean对象instanceWrappercreateBeanInstance(beanName,mbd,args);}ObjectbeaninstanceWrapper.getWrappedInstance();Class?beanTypeinstanceWrapper.getWrappedClass();// 2. 属性注入给Bean的属性赋值populateBean(beanName,mbd,instanceWrapper);// 3. 初始化Bean执行初始化逻辑ObjectexposedObjectinitializeBean(beanName,exposedObject,mbd);// 4. 注册销毁方法try{registerDisposableBeanIfNecessary(beanName,bean,mbd);}catch(BeanDefinitionValidationExceptionex){thrownewBeanCreationException(mbd.getResourceDescription(),beanName,Invalid destruction signature,ex);}returnexposedObject;}所有核心逻辑都在这四个方法里我们一个个拆解。二、生命周期各阶段详细拆解2.1 阶段1实例化createBeanInstance这个阶段就是调用Bean的构造方法创建一个空的对象还没有给属性赋值。Spring会根据Bean的配置选择合适的构造方法如果有默认无参构造方法优先调用无参构造。如果只有一个有参构造方法就调用这个有参构造自动注入构造方法的参数。如果有多个有参构造方法需要用Autowired注解指定用哪个。踩坑经验如果Bean只有有参构造方法而且参数的Bean不存在就会报错所以建议每个Bean都提供一个无参构造方法避免出现实例化失败的问题。2.2 阶段2属性注入populateBean实例化完成后就会给Bean的属性赋值比如Autowired、Value、Resource注解注入的属性都是在这个阶段赋值的。Spring会先解析Bean的所有属性然后根据属性的类型或者名称从容器里找到对应的Bean赋值到当前Bean的属性上。2.3 阶段3初始化initializeBean这个阶段是Bean初始化的核心我们看initializeBean的源码protectedObjectinitializeBean(StringbeanName,Objectbean,NullableRootBeanDefinitionmbd){// 执行Aware接口的方法if(System.getSecurityManager()!null){AccessController.doPrivileged((PrivilegedActionObject)()-{invokeAwareMethods(beanName,bean);returnnull;},getAccessControlContext());}else{invokeAwareMethods(beanName,bean);}ObjectwrappedBeanbean;// 执行BeanPostProcessor的前置处理方法if(mbdnull||!mbd.isSynthetic()){wrappedBeanapplyBeanPostProcessorsBeforeInitialization(wrappedBean,beanName);}// 执行初始化方法try{invokeInitMethods(beanName,wrappedBean,mbd);}catch(Throwableex){thrownewBeanCreationException((mbd!null?mbd.getResourceDescription():null),beanName,Invocation of init method failed,ex);}// 执行BeanPostProcessor的后置处理方法if(mbdnull||!mbd.isSynthetic()){wrappedBeanapplyBeanPostProcessorsAfterInitialization(wrappedBean,beanName);}returnwrappedBean;}这个方法里分三步第一步执行Aware接口方法如果Bean实现了Aware接口就会执行对应的方法常见的Aware接口有BeanNameAware注入当前Bean的名称BeanFactoryAware注入BeanFactory实例ApplicationContextAware注入ApplicationContext实例EnvironmentAware注入Environment实例用来获取配置第二步BeanPostProcessor前置处理执行所有BeanPostProcessor的postProcessBeforeInitialization方法在初始化之前对Bean做修改比如我们之前讲的参数脱敏就是在这里做代理的。第三步执行初始化方法初始化方法的执行顺序是执行PostConstruct注解标注的方法执行InitializingBean接口的afterPropertiesSet方法执行自定义的init-method方法在Bean注解里指定initMethod参数第四步BeanPostProcessor后置处理执行所有BeanPostProcessor的postProcessAfterInitialization方法在初始化之后对Bean做增强比如AOP的动态代理就是在这里生成的。到这里Bean的初始化就完成了这个Bean就可以正常使用了。2.4 阶段4销毁当Spring容器关闭的时候会执行Bean的销毁方法执行顺序是执行PreDestroy注解标注的方法执行DisposableBean接口的destroy方法执行自定义的destroy-method方法在Bean注解里指定destroyMethod参数三、循环依赖解决方案三级缓存原理循环依赖是面试必问的考点比如A依赖BB依赖ASpring是怎么解决这个问题的答案就是三级缓存。3.1 什么是三级缓存Spring的三级缓存是三个Map// 一级缓存保存已经创建完成的BeanprivatefinalMapString,ObjectsingletonObjectsnewConcurrentHashMap(256);// 二级缓存保存提前暴露的半成品Bean已经实例化还没初始化privatefinalMapString,ObjectearlySingletonObjectsnewHashMap(16);// 三级缓存保存Bean的工厂对象用来生成代理对象privatefinalMapString,ObjectFactory?singletonFactoriesnewHashMap(16);3.2 循环依赖解决流程我们以A依赖BB依赖A为例看解决流程开始创建A实例化A还没属性注入把A的工厂对象放到三级缓存里。给A注入属性B发现B还没创建开始创建B。实例化B还没属性注入把B的工厂对象放到三级缓存里。给B注入属性A从三级缓存里拿到A的工厂对象生成A的提前暴露对象放到二级缓存里删除三级缓存里的A的工厂对象。B注入A成功继续执行B的初始化B创建完成放到一级缓存里。回到A的创建流程注入B成功继续执行A的初始化A创建完成放到一级缓存里删除二级缓存里的A。循环依赖解决完成。3.3 常见面试问题为什么要用三级缓存二级缓存能不能解决循环依赖答如果没有AOP的话二级缓存就能解决循环依赖但如果有AOP的话需要生成代理对象三级缓存的ObjectFactory就是用来生成代理对象的提前暴露的对象必须是代理对象而不是原始对象所以需要三级缓存。构造方法注入的循环依赖为什么解决不了答因为构造方法注入的时候实例化A的时候就需要B的实例这时候A还没实例化完成没法放到三级缓存里所以解决不了。可以用Lazy注解延迟加载B实例化A的时候先注入B的代理对象等真正用到B的时候再创建B的实例。原型Bean的循环依赖为什么解决不了答原型Bean不会放到缓存里每次创建都是新的对象所以没法提前暴露对象解决不了循环依赖。四、各扩展点在生命周期中的触发时机我们之前讲过的5个高级扩展点在生命周期中的触发顺序是BeanFactoryPostProcessor在所有Bean实例化之前执行用来修改Bean定义。ApplicationContextInitializer在上下文刷新之前执行比BeanFactoryPostProcessor还早。BeanPostProcessor.postProcessBeforeInitialization在Bean初始化之前执行。InitializingBean.afterPropertiesSet在Bean初始化阶段执行。BeanPostProcessor.postProcessAfterInitialization在Bean初始化之后执行。ApplicationListener监听对应的事件在事件发布的时候执行。SpringBootExceptionReporter启动失败的时候执行。我之前面试的时候经常问这个问题很多人都搞不清顺序其实只要记住越宏观的扩展点执行越早Bean级别的扩展点在Bean创建的时候执行。五、面试高频考点提炼简述Spring Bean的生命周期PostConstruct、afterPropertiesSet、init-method的执行顺序Spring怎么解决循环依赖三级缓存的作用是什么构造方法注入的循环依赖怎么解决BeanPostProcessor的执行时机有什么作用Aware接口有哪些作用是什么PreDestroy、destroy、destroy-method的执行顺序原型Bean的循环依赖能不能解决为什么六、踩坑经验总结PostConstruct方法不要做耗时操作PostConstruct是同步执行的会阻塞Bean的创建导致启动慢耗时的操作要放到ApplicationRunner或者异步线程里执行。不要在初始化方法里依赖还没创建的Bean如果你依赖的Bean还没创建会报错可以用DependsOn注解指定依赖的Bean的创建顺序。循环依赖尽量避免虽然Spring能解决setter注入的循环依赖但循环依赖说明你的代码设计有问题尽量优化代码结构避免循环依赖。销毁方法要释放资源比如数据库连接、线程池、文件流这些资源要在销毁方法里释放避免资源泄漏。总结Bean的生命周期是Spring的核心理解了这个你才能看懂Spring的源码才能用好Spring的各种扩展点。大家不用死记硬背多看几遍doCreateBean的源码自己写几个测试例子打断点调试一下很快就能理解了。面试的时候按照这个流程答面试官肯定会对你刮目相看。