spring源码篇——BeanDefinition的注册
spring-framework 版本:v5.3.19
文章目录
- 注解方式(AnnotationConfigApplicationContext)
- AnnotationConfigApplicationContext#register
- AnnotatedBeanDefinitionReader#doRegisterBean
- BeanDefinitionRegistry#registerBeanDefinition
- AnnotationConfigApplicationContext#scan
- ClassPathBeanDefinitionScanner#doScan
- findCandidateComponents
- BeanDefinitionRegistry#registerBeanDefinition
- XML方式(AbstractXmlApplicationContext)
- AbstractXmlApplicationContext#loadBeanDefinitions
- XmlBeanDefinitionReader#doLoadBeanDefinitions
- BeanDefinitionDocumentReader#doRegisterBeanDefinitions
- BeanDefinitionRegistry#registerBeanDefinition
- 总结
注解方式(AnnotationConfigApplicationContext)
AnnotationConfigRegistry接口有两个方法,分别对应了注解生成beanDefinition的两种方式
来到其实现类AnnotationConfigApplicationContext可以发现类里有两个成员变量reader和scanner
reader对应register方法(根据传入class生成beanDefinition)
scanner对应scan方法(根据传入包名扫描特定注解表示的class生成beanDefinition)
先看register方法的实现
AnnotationConfigApplicationContext#register
AnnotatedBeanDefinitionReader#doRegisterBean
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,@Nullable BeanDefinitionCustomizer[] customizers) {//根据bean类生成beanDefinition(实际上就是解析出 AnnotationMetadata 元数据)AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);//根据condition注解条件判断是否跳过if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {return;}//创建bean之后的回调方法abd.setInstanceSupplier(supplier);//设置作用域ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);abd.setScope(scopeMetadata.getScopeName());//得到beanNameString beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));//通用注解解析:Lazy、Primary、DependsOn、Role、DescriptionAnnotationConfigUtils.processCommonDefinitionAnnotations(abd);if (qualifiers != null) {for (Class<? extends Annotation> qualifier : qualifiers) {if (Primary.class == qualifier) {abd.setPrimary(true);}else if (Lazy.class == qualifier) {abd.setLazyInit(true);}else {abd.addQualifier(new AutowireCandidateQualifier(qualifier));}}}//自定义处理if (customizers != null) {for (BeanDefinitionCustomizer customizer : customizers) {customizer.customize(abd);}}//封装成BeanDefinitionHolderBeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);//是否需要代理definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);//注册beanDefinition到registryBeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);}
BeanDefinitionRegistry#registerBeanDefinition
进入BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry)方法
可以发现最终会调用BeanDefinitionRegistry.registerBeanDefinition方法。但这是一个接口,其对应的实现类是哪个呢?
其实就是AnnotationConfigApplicationContext本身。
在赋值reader的时候会把自身传进去赋值给register,而AnnotationConfigApplicationContext继承自GenericApplicationContext,GenericApplicationContext又继承自BeanDefinitionRegistry,所以AnnotationConfigApplicationContext也是一个BeanDefinitionRegistry。
GenericApplicationContext.registerBeanDefinition实际上只是调用了另一个实现类DefaultListableBeanFactory的registerBeanDefinition方法
reader赋值
GenericApplicationContext.registerBeanDefinition
综上最终会来到DefaultListableBeanFactory#registerBeanDefinition,也即BeanDefinitionRegistry#registerBeanDefinition
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {Assert.hasText(beanName, "Bean name must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");//抽象beanDefinition的一些处理(beanDefinition也有继承关系)if (beanDefinition instanceof AbstractBeanDefinition) {try {((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Validation of bean definition failed", ex);}}//beanName是否已经存在beanDefinitionBeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);if (existingDefinition != null) {//不允许覆盖则报错if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);}//打印一些日志else if (existingDefinition.getRole() < beanDefinition.getRole()) {// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTUREif (logger.isInfoEnabled()) {logger.info("Overriding user-defined bean definition for bean '" + beanName +"' with a framework-generated bean definition: replacing [" +existingDefinition + "] with [" + beanDefinition + "]");}}else if (!beanDefinition.equals(existingDefinition)) {if (logger.isDebugEnabled()) {logger.debug("Overriding bean definition for bean '" + beanName +"' with a different definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}else {if (logger.isTraceEnabled()) {logger.trace("Overriding bean definition for bean '" + beanName +"' with an equivalent definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}//覆盖原beanDefinitionthis.beanDefinitionMap.put(beanName, beanDefinition);}else {//判断是否有bean开始注册了,有就加锁保证线程安全if (hasBeanCreationStarted()) {synchronized (this.beanDefinitionMap) {this.beanDefinitionMap.put(beanName, beanDefinition);List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);updatedDefinitions.addAll(this.beanDefinitionNames);updatedDefinitions.add(beanName);this.beanDefinitionNames = updatedDefinitions;removeManualSingletonName(beanName);}}//没有就直接注册beanDefinitionelse {this.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);removeManualSingletonName(beanName);}this.frozenBeanDefinitionNames = null;}//如果存在过beanName对应的beanDefinition或者单例bean则重置beanDefinition//移除合并的beanDefinition、销毁单例bean、执行MergedBeanDefinitionPostProcessor.resetBeanDefinitionif (existingDefinition != null || containsSingleton(beanName)) {resetBeanDefinition(beanName);}//如果beanDefinition被冻结的一些处理else if (isConfigurationFrozen()) {clearByTypeCache();}}
至此beanDefinition就注册到容器上了。
接下来扫描包的方式即scan方法
AnnotationConfigApplicationContext#scan
ClassPathBeanDefinitionScanner#doScan
![ protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();for (String basePackage : basePackages) {//扫描生成BeanDefinitionSet<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {//得到作用域元数据ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);//设置作用域candidate.setScope(scopeMetadata.getScopeName());//得到beanNameString beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}//通用注解解析:Lazy、Primary、DependsOn、Role、Descriptionif (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}//检测BeanDefinition的beanName是否与其他BeanDefinition存在冲突//虽然是一个判断,当时会有三种情况:true:beanName没有冲突,false:有冲突但兼容,抛异常:有冲突且不兼容if (checkCandidate(beanName, candidate)) {//没有冲突则注册BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);//注册beanDefinitionregisterBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}]
findCandidateComponents
findCandidateComponents方法返回Set < BeanDefinition > 。可以发现当有指定spring.components文件时,会直接用该文件加快加载,否则扫描包下所有资源。
scanCandidateComponents
这里会先得到包下所有的Resource,再依次获取元数据封装到MetadataReader,当满足excludeFilter/includeFilter/condition条件时,创建ScannedGenericBeanDefinition。
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}try {//得到元数据MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);//判断excludeFilter/includeFilter/condition条件if (isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setSource(resource);//判断excludeFilter/includeFilter/condition条件if (isCandidateComponent(sbd)) {if (debugEnabled) {logger.debug("Identified candidate component class: " + resource);}//添加扫描beanDefinitioncandidates.add(sbd);}//以下都是日志异常处理......
isCandidateComponent
BeanDefinitionRegistry#registerBeanDefinition
进入registerBeanDefinition(definitionHolder, this.registry);
可以发现最后跟register一样调用BeanDefinitionReaderUtils#registerBeanDefinition进而调用BeanDefinitionRegistry#registerBeanDefinition,这里就不再说一遍了。
XML方式(AbstractXmlApplicationContext)
AbstractXmlApplicationContext#loadBeanDefinitions
AbstractXmlApplicationContext会调用BeanDefinitionReader#loadBeanDefinitions加载xml文件,而xml方式具体的实现类就是XmlBeanDefinitionReader。
虽然XmlBeanDefinitionReader和上面提到的AnnotatedBeanDefinitionReader都是以BeanDefinitionReader结尾,但是实际上这两个从类继承来看没有什么关系,并不来自于同一基类也不互相继承。XmlBeanDefinitionReader实现了BeanDefinitionReader接口,而AnnotatedBeanDefinitionReader不实现任何接口也不继承任何类。
跟踪XmlBeanDefinitionReader代码来到真正做事的地方:doLoadBeanDefinitions方法
XmlBeanDefinitionReader#doLoadBeanDefinitions
最终来到BeanDefinitionDocumentReader.doRegisterBeanDefinitions()方法
BeanDefinitionDocumentReader#doRegisterBeanDefinitions
这一步主要就是对各种标签的解析
BeanDefinitionRegistry#registerBeanDefinition
进入bean标签的解析
可以发现最后同样调用BeanDefinitionReaderUtils#registerBeanDefinition进而调用BeanDefinitionRegistry#registerBeanDefinition,剩下的步骤就跟注解方式的一样了。
总结
注解方式可register直接注册,也可scan扫描注册。register方式通过AnnotatedBeanDefinitionReader创建beanDefinition(同时会得到元数据),再根据元数据等设置完beanDefinition属性后,用BeanDefinitionRegistry注册到容器。scan方式通过ClassPathBeanDefinitionScanner先扫描生成所有的BeanDefinition(同时会得到元数据),依次遍历BeanDefinition,根据元数据等设置BeanDefinition的属性,最后检查是否冲突,当无冲突时也用BeanDefinitionRegistry注册到容器。
xml方式通过XmlBeanDefinitionReader先加载xml文件并封装成document,再将document委托给BeanDefinitionDocumentReader对各种标签进行解析生成beanDefinition,最后用BeanDefinitionRegistry注册到容器中。
可以发现无论是注解register还是注解scan还是xml最终在注册到容器时都会用到BeanDefinitionRegistry,而其具体的实现就是DefaultListableBeanFactory#registerBeanDefinition。
相关文章:

spring源码篇——BeanDefinition的注册
spring-framework 版本:v5.3.19 文章目录注解方式(AnnotationConfigApplicationContext)AnnotationConfigApplicationContext#registerAnnotatedBeanDefinitionReader#doRegisterBeanBeanDefinitionRegistry#registerBeanDefinitionAnnotatio…...

virtualbox7虚拟机中安装苹果macOS big sur系统详细教程
第1步,在 Windows 10/11 PC 上启用虚拟化。 现在的电脑一般都默认开启虚拟化技术了。 如果你遇到一些报错,比如收到错误消息“无法在虚拟机上打开会话”,可以查看 如果没有遇到问题,可以直接进入到第二步。 第2步,在…...

用spectralayers 简单去一下人声做个伴奏
最近有个同事说有个工作要一个歌的伴奏不会下载问我能不能给下一个。问题是我五音不全,也不咋关注伴奏这方面的事儿,然后巧了,当天晚上就有个网上的大哥在群里聊天的时候说有个去人声比较给力的软件,我马上给要来了。 软件叫啥sp…...
高峰对话|深度探讨「多云与边缘」
2022 年 12 月,分析师 Zeus Kerravala 与 VMware 通信运营商和边缘事业部高级副总裁兼总经理 Sanjay Uppal 进行非常有启发性的谈话,分享了科技行业领导者的见解。 二位主要围绕以下主题进行探讨: 📍 如何定义多云,以…...
开发手册——一、编程规约_2.常量定义
这篇文章主要梳理了在java的实际开发过程中的编程规范问题。本篇文章主要借鉴于《阿里巴巴java开发手册终极版》 下面我们一起来看一下吧。 1. 【强制】不允许任何魔法值(即未经定义的常量)直接出现在代码中。 反例:String key "Id#…...

Sandstorm 建设者亮点——2023 年 2 月
隆重推出 Sandstorm 建设者亮点——2023 年 2 月版,这是由最厉害的 Sandstorm 社区制作的独一无二的 NFT 系列。 从突破性的兔子机器人到神奇的蒸汽朋克海盗船,Sandstorm 建设者亮点 NFT 系列展示了一系列独一无二的创作。 19 项新资产将添加至 Sandstor…...

MyBatis快速入门
创建表(自行完成)创建模块,引入坐标(1).进入mybatis官网:MyBatis中文网按步骤进行添加坐标先添加mybatis依赖然后手动添加mysql驱动junit单元测试坐标:logback坐标:用的时候直接复制…...
Mysql的一些提权方式(mysql提权、UDF)
目录 bash命令提权 必要条件 实验 UDF提权 什么是UDF 必要条件 实验 手动测试...
【2023】DevOps、SRE、运维开发面试宝典之Docker相关面试题
文章目录 1、docker的工作原理是什么2、docker的组成包含哪几大部分3、讲一下镜像的分层结构以及为什么要使用镜像的分层结构?4、简单描述一下Dockerfile的整个构建镜像过程?5、Docker的四种网络类型?6、Docker跨宿主机通讯的方式1、docker的工作原理是什么 docker是一个Cl…...

圣杯布局的实现方式
1.什么是圣杯布局? 左右盒子固定,中间盒子自适应 2.实现方式 (1)flex布局 思路:左右盒子给固定的宽高,中间盒子flex:1 <!DOCTYPE html> <html lang"en"> <head> <met…...
RecastDemo用法
这里写自定义目录标题recastnavigation介绍recastnavigation的内容RecastDemo安装RecastDemo介绍可配置参数合理的创建标题,有助于目录的生成如何改变文本的样式生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个…...

IIC总线式驱动开发(mpu6050)(二)
目录 六、I2C总线二级外设驱动开发方法 七、I2C总线二级外设驱动开发之名称匹配 1. i2c_register_board_info 2. i2c_new_device:明确二级外设地址的情况下可用 3. i2c_new_probed_device 八、I2C总线二级外设驱动开发之设备树匹配 六、I2C总线二级外设驱动开…...

盘点一下那些远程办公的神仙公司
其实远程办公已经有50多年的历史了,这几年,这种工作方式越来越受到大家的喜欢,对于员工来说,工作效率可以大幅提高,节省下来的通勤时间和成本,有更多的时间花在工作上。可以更好的平衡工作与生活。对于公司…...

Spring Cloud Alibaba全家桶(四)——微服务调用组件Feign
前言 本文小新为大家带来 微服务调用组件Feign 的相关知识,具体内容包含什么是Feign,Spring Cloud Alibaba快速整合OpenFeign,Spring Cloud Feign的自定义配置及使用(包括:日志配置、契约配置、自定义拦截器实现认证逻…...

安装pytorch
一、在anaconda中创建虚拟环境 打开Anaconda Prompt创建一个虚拟环境。比如要创建一个名字为pytorch的虚拟环境,可以如下输入。其中python3.7指定该虚拟环境的python版本号。 conda create -n pytorch python3.7 二、进入新创建的虚拟环境。 创建好虚拟环境后&a…...

自动化测试 Appium之Python运行环境搭建 Part2
环境部署 1、安装Android SDK 安装好后,配置ANDROID_HOME环境变量,设置为Android SDK安装路径(例中:D:\Program Files (x86)\Android\android-sdk) 2、安装其它SDK相关软件包 安装好Android SDK后,选择打开Android SDK Manager…...

LeetCode 2 - 两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会以 0 开…...

用Python实现九九乘法表的几种方式,最简单只需一行代码
前言 嗨喽~大家好呀,这里是魔王呐 ❤ ~! 我们在学习Python的过程中需要不断的积累和练习,这样才能够走的更远, 今天一起来学习怎么用Python写九九乘法表~ 更多教程源码资料电子书: 点击此处跳转文末名片获取 第一种方法、for-for 代码&…...

入职外包三个月,我提桶跑路了
有一种打工人的羡慕,叫做“大厂”。 真是年少不知大厂香,错把青春插稻秧。 但是,在深圳有一群比大厂员工更庞大的群体,他们顶着大厂的“名”,做着大厂的工作,还可以享受大厂的伙食,却没有大厂…...

企业邮箱对企业有哪些好处以及便捷性
企业邮箱拥有更专业的办公功能,更适合职场使用。同时,使用企业邮箱还可以帮助企业“公私分明”。一方面保护了公司信息,另一方面也提高了工作效率。加上公司统一邮箱也有助于提升公司形象。使用企业邮箱除了收发邮件方便外,还可以…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...