Bean 是 Spring 应用的核心组件,而 BeanDefinition 作为 Bean 的 “元数据描述”,贯穿了 Bean 从定义到销毁的全生命周期。理解 BeanDefinition 的加载注册机制,以及 Bean 的完整生命周期,是掌握 Spring 容器管理逻辑的关键,也是面试中的高频深挖点。本文结合源码与面试场景,解析核心流程与实战要点。

一、BeanDefinition:Bean 的 “蓝图” 与加载注册机制

面试常问:Spring 是如何识别并管理 Bean 的?BeanDefinition 在其中扮演什么角色?

1. BeanDefinition 的核心作用

BeanDefinition 是 Spring 对 Bean 的 “抽象描述”,包含了创建 Bean 所需的全部信息:

  • 类信息:Bean 的全限定类名(getBeanClassName());
  • 作用域:单例(singleton)或原型(prototype,默认单例);
  • 属性值:Bean 的属性及依赖(如@Autowired注入的对象);
  • 初始化与销毁方法:如@PostConstruct标注的方法、init-method配置;
  • 懒加载标识:是否延迟初始化(@Lazy注解对应isLazyInit())。

Spring 容器通过 BeanDefinition 的信息 “照图施工”,创建并管理 Bean 实例。可以说,BeanDefinition 是 Bean 的 “蓝图”,容器的所有操作都基于此蓝图展开。

2. BeanDefinition 的加载与注册流程(源码解析)

BeanDefinition 的加载注册是容器初始化的核心环节,以注解配置(@Component、@Bean)为例,流程如下:

(1)资源定位与扫描
  • 触发点:@ComponentScan注解指定扫描路径,由ConfigurationClassPostProcessor(BeanFactoryPostProcessor 的实现类)执行扫描。
  • 核心逻辑:ClassPathBeanDefinitionScanner.scan()方法遍历指定包路径,通过ClassPathScanningCandidateComponentProvider筛选符合条件的类(如带@Component、@Service等注解的类)。
(2)BeanDefinition 的生成

扫描到的类会被解析为 BeanDefinition 实例(默认GenericBeanDefinition):

// 简化逻辑:为目标类创建BeanDefinitionGenericBeanDefinition bd = new GenericBeanDefinition();bd.setBeanClassName(clazz.getName()); // 设置类名bd.setScope(BeanDefinition.SCOPE_SINGLETON); // 设置作用域bd.setLazyInit(false); // 非懒加载
(3)注册到容器

生成的 BeanDefinition 会被注册到DefaultListableBeanFactory的beanDefinitionMap(一个ConcurrentHashMap)中:

// DefaultListableBeanFactory的注册方法public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {this.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);}
  • 注册后:容器通过beanName即可从beanDefinitionMap中获取 BeanDefinition,为后续 Bean 的创建提供依据。

面试应答技巧:回答 “BeanDefinition 的作用” 时,可类比 “建筑图纸”—— 设计师(开发者)绘制图纸(定义 Bean),施工队(Spring 容器)根据图纸建造房屋(创建 Bean),图纸的修改(BeanDefinition 的动态调整)会直接影响最终建筑(Bean 实例)。

二、Bean 的完整生命周期:从实例化到销毁的全链路

面试高频问:Bean 从创建到销毁经历哪些阶段?哪些扩展点可以干预 Bean 的生命周期?

Bean 的生命周期可概括为 “实例化→属性填充→初始化→使用→销毁” 五个阶段,每个阶段都有对应的扩展点(如后置处理器)。以单例 Bean 为例,核心流程如下:

1. 实例化(Instantiation)

  • 作用:创建 Bean 的实例(内存分配),尚未进行属性设置。
  • 实现:通过反射调用 Bean 的构造方法(BeanUtils.instantiateClass())。
  • 扩展点:InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation(),可在实例化前返回代理对象,跳过默认实例化流程(如 AOP 代理创建)。

2. 属性填充(Population)

  • 作用:为 Bean 的属性赋值,包括依赖注入(如@Autowired、@Resource)。
  • 实现:AbstractAutowireCapableBeanFactory.populateBean(),通过AutowiredAnnotationBeanPostProcessor解析@Autowired注解,完成依赖注入。
  • 关键逻辑:从容器中查找依赖的 Bean,若依赖未创建则触发其生命周期(递归依赖处理)。

3. 初始化(Initialization)

初始化是 Bean 生命周期中扩展点最丰富的阶段,核心步骤:

(1)执行 Aware 接口回调

Spring 通过 Aware 接口向 Bean 暴露容器内部组件,常见接口:

  • BeanNameAware:注入当前 Bean 的名称;
  • BeanFactoryAware:注入 BeanFactory;
  • ApplicationContextAware:注入 ApplicationContext。

回调逻辑在AbstractAutowireCapableBeanFactory.invokeAwareMethods()中实现:

private void invokeAwareMethods(String beanName, Object bean) {if (bean instanceof BeanNameAware) {((BeanNameAware) bean).setBeanName(beanName);}if (bean instanceof BeanFactoryAware) {((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);}// 其他Aware接口回调...
}
(2)执行 BeanPostProcessor 的前置处理

BeanPostProcessor.postProcessBeforeInitialization():在初始化方法执行前对 Bean 进行加工,例如ApplicationContextAwareProcessor会处理ApplicationContextAware接口的回调。

(3)执行自定义初始化方法
  • @PostConstruct 注解:由CommonAnnotationBeanPostProcessor解析并执行;
  • init-method 配置:XML 中init-method属性指定的方法,或@Bean(initMethod = "...");
  • InitializingBean 接口:执行afterPropertiesSet()方法。

执行顺序:@PostConstruct → InitializingBean.afterPropertiesSet() → init-method。

(4)执行 BeanPostProcessor 的后置处理

BeanPostProcessor.postProcessAfterInitialization():在初始化方法执行后对 Bean 进行加工,AOP 代理就是在此步骤创建的(AbstractAutoProxyCreator的核心逻辑)。

4. 使用阶段

Bean 初始化完成后,存入容器的单例池(singletonObjects),供应用程序通过getBean()获取使用。

5. 销毁阶段

  • 触发时机:容器关闭时(如ApplicationContext.close())。
  • 执行逻辑
  1. @PreDestroy注解标注的方法;
  2. DisposableBean.destroy()方法;
  3. XML 中destroy-method属性或@Bean(destroyMethod = "...")指定的方法。

3. 生命周期扩展点实战(面试场景题)

场景:如何在 Bean 初始化后自动注册到某个管理中心(如缓存管理器、服务注册中心)?

解决方案:自定义BeanPostProcessor,在postProcessAfterInitialization()中实现注册逻辑:

@Component
public class RegistryBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {// 对特定类型的Bean进行注册if (bean instanceof Cacheable) {CacheManager.register((Cacheable) bean);}return bean;}
}

面试考点:BeanPostProcessor与BeanFactoryPostProcessor的区别?

  • BeanFactoryPostProcessor:作用于 BeanDefinition,可修改 Bean 的元数据(如修改属性值);
  • BeanPostProcessor:作用于 Bean 实例,可修改 Bean 本身(如创建代理、添加属性)。

三、面试高频问题与应答框架

1. 问:Bean 的实例化与初始化有什么区别?

应答框架

“实例化和初始化是 Bean 生命周期的两个不同阶段。

  • 实例化(Instantiation)是‘创建对象’的过程,通过反射调用构造方法分配内存,此时 Bean 还未设置属性,处于‘半成品’状态;
  • 初始化(Initialization)是‘完善对象’的过程,在属性填充之后,会执行 Aware 接口回调、@PostConstruct方法等,最终将 Bean 变为‘成品’。

简单说,实例化是‘从无到有’创建对象,初始化是‘从有到优’完善对象。”

2. 问:Spring 如何解决循环依赖?(核心难点)

应答框架

“Spring 通过‘三级缓存’机制解决单例 Bean 的循环依赖(如 A 依赖 B,B 依赖 A):

  • 一级缓存(singletonObjects):存储初始化完成的 Bean;
  • 二级缓存(earlySingletonObjects):存储实例化完成但未初始化的 Bean;
  • 三级缓存(singletonFactories):存储 Bean 的工厂对象,用于生成早期代理对象。

解决流程:

  1. A 实例化后,将其工厂对象放入三级缓存;
  1. A 需要注入 B,触发 B 的实例化;
  1. B 实例化后需要注入 A,从三级缓存获取 A 的早期对象(若有 AOP 则生成代理),放入二级缓存;
  1. B 初始化完成,放入一级缓存,A 注入 B 后继续初始化,最终放入一级缓存。

注意:原型 Bean 的循环依赖无法解决,会抛出BeanCurrentlyInCreationException。”

3. 问:@Autowired注入发生在 Bean 生命周期的哪个阶段?

应答框架

“@Autowired注入发生在属性填充阶段(populateBean()方法),在实例化之后、初始化之前。

具体来说,由AutowiredAnnotationBeanPostProcessor(InstantiationAwareBeanPostProcessor的实现类)的postProcessProperties()方法完成:

  1. 解析 Bean 中的@Autowired注解,找到依赖的 Bean;
  2. 从容器中获取依赖的 Bean(若未创建则触发其生命周期);
  3. 将依赖注入到当前 Bean 的属性中。

因此,在@PostConstruct标注的初始化方法中,可以安全使用@Autowired注入的依赖。”

四、实战总结

BeanDefinition 的加载注册是 Spring 管理 Bean 的基础,而 Bean 的生命周期则体现了容器对 Bean 的 “全生命周期管理”。掌握这些知识,不仅能应对面试中的深度提问,更能在实战中通过扩展点(如BeanPostProcessor)定制 Bean 的行为,解决复杂业务场景问题。

下一篇将解析 Spring AOP 的底层实现,包括动态代理选择逻辑、切面织入流程及@Transactional注解的原理,这也是面试中的重点难点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/92747.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/92747.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/92747.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

node.js 学习笔记2 进程/线程、fs

进程和线程 进程:进行中的程序。比如有一段程序,程序已经载入内存了,CPU正在执行这段程序,这时候就会产生一个进程。进程,也可以看做程序的一次执行过程。 在window中打开任务管理器,可以查看计算机中的所…

【线性代数】其他

上一节:【线性代数】线性方程组与矩阵——(3)线性方程组解的结构 总目录:【线性代数】目录 文章目录11. 向量的内积、长度及正交性12. 方阵的特征值与特征向量13. 相似矩阵14. 对称矩阵的对角化15. 二次型及其标准形11. 向量的内积…

Spring Cloud LoadBalancer 实现自定义负载均衡策略(基于服务元数据筛选)

💡 Spring Cloud LoadBalancer 实现自定义负载均衡策略(基于服务元数据筛选) 在微服务架构中,我们常常希望对服务实例进行更精细的路由控制,例如: 灰度发布:不同环境访问不同版本操作系统差异&a…

Javaweb(1)html、css、js

注:图来自黑马 一、HTML(超文本标记语言) HTML 是网页的 “骨架”,负责定义页面的结构和内容,通过标签(tag)描述文本、图片、链接等元素。 1. 基础结构 文档声明:<!DOCTYPE html>(告诉浏览器这是 HTML5 文档)。 根标签:<html> 包裹整个文档,包含 &l…

MQTT:Dashboard数据集成(待补充)

目录一、工作原理二、基本使用三、连接器基本使用一、工作原理 数据集成使用sink和source组件与外部数据系统对接。 sink&#xff1a;用于将消息发送到外部数据系统&#xff0c;例如MySQL、Kafka或Http服务等。source&#xff1a;用于从外部数据系统接收消息&#xff0c;例如…

VisionMoE本地部署的创新设计:从架构演进到高效实现

本地部署VisionMoE的时代需求 在人工智能技术飞速发展的今天&#xff0c;视觉语言模型(Vision-Language Models, VLMs)已成为多模态理解的核心工具。然而&#xff0c;传统的大型视觉语言模型主要依赖云端GPU集群进行部署和推理&#xff0c;这不仅带来了高昂的运营成本&#xf…

机试备考笔记 8/31

2025年8月8日 小结&#xff1a;省流&#xff0c;写了俩道巨简单的&#xff08;被卡好久的传参指针和指针的引用的区别&#xff09;&#xff0c;一题递归&#xff08;意满&#xff09;&#xff1b;这笔记还是0809写的&#xff0c;啧&#xff0c;今天可能不写了&#xff0c;明天也…

java9学习笔记-part2

进程 API在 Java 9 之前&#xff0c;Process API 仍然缺乏对使用本地进程的基本支持&#xff0c;例如获取进程的 PID 和所有者&#xff0c;进程的开始时间&#xff0c;进程使用了多少 CPU 时间&#xff0c;多少本地进程正在运行等。Java 9 向 Process API 添加了一个名为 Proce…

AI智能编程工具汇总

AI智能编程工具汇总 以下是一份关于主流大模型开发工具的综合介绍&#xff0c;涵盖 Gemini CLI、Qwen-Code、Kimi K2 等关键工具的功能特性、安装方式与使用建议。 &#x1f31f; Gemini CLI 开发者&#xff1a;Google DeepMind 简介&#xff1a;命令行工具&#xff0c;用于调…

算法_python_牛客华为机试笔记_01

刷题是必须的&#xff0c;通过刷题以及别人对题目的解析&#xff0c;可以快速理解&#xff0c;提高效率。 00_题库与参考视频 华为机试_在线编程_牛客网 HJ3 明明的随机数_哔哩哔哩_bilibili 这套华为机试是华为笔试面试机考在线练习&#xff0c;共138道题&#xff0c;目前…

Java基础-完成局域网内沟通软件的开发

目录 案例要求&#xff1a; 实现思路&#xff1a; itheima-chat-server包 src com.itheima Constant类&#xff1a; Server类: ServerReaderThread类: itheima-chat-system包 src com.itheima.ui ChatEntryFrame类&#xff1a; ClientChatFrame类: ClientReaderTh…

windows内核研究(内存管理-线性地址的管理)

内存管理线性地址的管理 进程空间的地址划分分区x86 32位Windows空指针赋值区0x00000000 - 0x0000FFFF用户模式区0x00010000 - 0x7FFEFFFF64KB禁入区0x7FFF0000 - 0x7FFFFFFF内核0x80000000 - 0xFFFFFFFF线性地址有4GB&#xff0c;但是并不是所有的地方都能访问&#xff08;这里…

【问题解决】使用patch-package修改node-models中的源码

文章目录一、应用场景二、patch-package 和 postinstallpatch-packagepostinstall三、操作步骤1、使用yarn安装patch-package和postinstall-postinstall2、修改package.json3、修改node-model中源码、保存。4、找到修改文件对应的包名5、使用git将新增的patches文件同步到仓库6…

当配置项只支持传入数字,即无法指定单位为rem,需要rem转px

您好&#xff01;针对您 Vue 3 Element Plus 的技术栈&#xff0c;要优雅且符合大厂规范地解决这个问题&#xff0c;最佳实践是创建一个响应式的 Composition API (组合式函数)。 这个方法完全遵循 Vue 3 的设计哲学&#xff0c;具有高内聚、低耦合、可复用、类型安全&#xf…

谷歌搜索 sg_ss 逆向分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;部分python代码sg_ss cp.call(get_sg_…

一个“加锁无效“的诡异现象

加锁了还出问题&#xff1f;从"点击过快"到"状态可控"&#xff1a;多线程共享变量的并发陷阱与实战对策详情如下&#xff1a;在服务端开发中&#xff0c;多线程并发处理客户端请求是提升系统吞吐量的常见手段。最近有位开发者朋友遇到了一个令人费解的问题…

液体泄漏识别误报率↓76%:陌讯多模态融合算法实战解析

原创声明本文为原创技术解析&#xff0c;核心技术参数与架构设计引用自《陌讯技术白皮书》&#xff0c;禁止未经授权的转载与篡改。一、行业痛点&#xff1a;液体泄漏识别的现实挑战在化工生产、食品加工、仓储物流等场景中&#xff0c;液体泄漏的实时监测是保障安全生产的关键…

Y9000P跑开源模型(未完成)

环境信息 1、Y9000笔记本 2、1T空白硬盘 3、ubunut24.04桌面版 一、环境初始化 第一部分&#xff1a;系统初始化 1、安装基础软件 apt-get update apt-get -y install openssh-server openssh-client apt-utils freeipmi ipmitool sshpass ethtool zip unzip nano less git ne…

ARM体系结构

ARM体系结构 编程原理 从源代码到CPU执行过程 #mermaid-svg-M4xemCxDjIQVNNnW {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:14px;fill:#333;}#mermaid-svg-M4xemCxDjIQVNNnW .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mer…

基于SpringBoot的高校社团管理系统的设计与实现(代码+LW文档+远程运行)

&#x1f4af;博主&#xff1a;✌全网拥有50W粉丝、博客专家、全栈领域优质创作者、平台优质Java创作者、专注于Java技术领域和毕业项目实战✌&#x1f4af; &#x1f497;开发技术&#xff1a;SpringBoot、Vue、SSM、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、…