003-依赖注入、属性赋值源码分析
目录
- 引入
- 作用
- 代码分析
- InstantiationAwareBeanPostProcessor#postProcessProperties()
- AutowiredAnnotationBeanPostProcessor
- 查找注入点元数据
- 给注入点注入属性
引入
之前我们了解到BeanDefinition到Bean,经历了
- 实例化
- 属性赋值
- 初始化
3个步骤现在详细分析下属性赋值:populateBean(beanName, mbd, instanceWrapper);
作用
就是查找Bean内字段或者方法上是否有@Autowired
如果有则从 context中查找并赋值,完成依赖注入
代码分析
其实就是解析各种依赖存入 getPropertyValues 缓存(相当于一个Map)
最后在统一反射到属性中
beanName – the name of the bean
mbd – the bean definition for the bean
bw – the BeanWrapper with bean instance
populateBean(beanName, mbd, instanceWrapper);ropertyValues pvs = mbd.getPropertyValues();//可以这样设置@Bean(autowire = Autowire.BY_NAME)
//这里只是解析 但是并没有真的设置 设值在最后一行
if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME || AUTOWIRE_BY_TYPE) {创建一个缓存 newPvs//意思是Bean的属性注入自动根据其Setter的名字或者属性if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {newPvs.put(属性的名字(propertyName), getBean(propertyName));}//也可以 Autowire.BY_TYPE 就是根据Setter 参数的类型if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {newPvs.put(属性的名字(propertyName), getBean(属性Setter的参数的类型));}pvs = newPvs;
}
//注意:这个方式已经被标记为过时的 因为这样调试起来很麻烦 而且不是很方便推荐还是@Autowired遍历处理器 InstantiationAwareBeanPostProcessor bp {pvs = bp.postProcessProperties();
}//根据pvs给对象设值
if (pvs != null) {//这里最终也是通过反射把值设置到字段中BeanWrapperImpl.BeanPropertyHandler#setValueapplyPropertyValues(beanName, mbd, bw, pvs);
}
InstantiationAwareBeanPostProcessor#postProcessProperties()
这个处理器就是用来专门处理Bean的属性的
AutowiredAnnotationBeanPostProcessor
这个处理器实现就是专门处理@Autowired之类的逻辑
初始化的时候autowiredAnnotationTypes设置了
- Autowired.class
- Value.class
- javax.inject.Inject
postProcessProperties()://查找注入点元数据
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
//给注入点注入属性
metadata.inject(bean, beanName, pvs);
查找注入点元数据
findAutowiringMetadata()://这里metadata 其实已经有值了
//在 MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
//其实就进入了AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
//在buildAutowiringMetadata()就把带注解的字段和方法解析完了
//并设置到injectedElements字段中
//最后 this.injectionMetadataCache.put(cacheKey, new InjectionMetadata(clazz, injectedElements)));
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
if (metadata == null) {metadata = buildAutowiringMetadata(clazz);this.injectionMetadataCache.put(beanName, );
}
//这里就是构建元数据
//构建元数据的前提是有需要注入的点 如果有则创建一个AutowiredFieldElement加入currElements
buildAutowiringMetadata(clazz):遍历所有的字段(field) {if(带有4个注解之一) {currElements.add(new AutowiredFieldElement(field, required));}
}遍历所有方法(method){if(带有4个注解之一) {PropertyDescriptor pd = 这个方法是否是某个字段的读写currElements.add(new AutowiredMethodElement(method, required, pd));}
}return new InjectionMetadata(currElements, clazz);
给注入点注入属性
AutowiredFieldElement#inject
//找到字段的值
value = resolveFieldValue(field, bean, beanName);
//反射
if (value != null) {ReflectionUtils.makeAccessible(field);field.set(bean, value);
}
resolveFieldValue(field, bean, beanName)://包装成统一的描述符
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
//找到值
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);return value;
resolveDependency(desc, beanName, autowiredBeanNames, typeConverter)://设置参数Name发现器
//1. -parameters java8 编译带了这个参数就能获取参数的name
//2. 本地变量表 基于ASM技术
descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
if (类型不是 ObjectFactory || Optional) {if(依赖带有@Lazy) {return 对名称和依赖进行代理(在用的时候再查找注入);} return doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter):if (descriptor储存了快速解析出来的对象) {return shortcut;
}Object value = 查找依赖是否有@Value, 并获取值
if (value != null && value instanceof String) {//解析 ${} 配置内容并获取值//计算解析不到也把#Value的值读取出来 后续el表达式解析String strVal = resolveEmbeddedValue((String) value);value = evaluateBeanDefinitionString(strVal, bd);return 类型转化(value);
}//这里就是判断依赖是否是复数的 下边的Object只是指代对象 不是特定的类型
//Map<String, Object> 会注入 BeanName : 符合要求的Bean对象 作为map的entry
//List<Object> 和 Object[] 会直接注入符合要求的所有Bean对象
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {return multipleBeans;
}//根据类型查找所有的符合要求的实例
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans 为空) {if (isRequired) {throw new NoSuchBeanDefinitionException()} return null;
}//确定依赖的实例和名称
String autowiredBeanName;
Object instanceCandidate;
if (matchingBeans.size() > 1) {autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);if (autowiredBeanName == null) {throw new NoUniqueBeanDefinitionException();}instanceCandidate = matchingBeans.get(autowiredBeanName);
} else {Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();autowiredBeanName = entry.getKey();instanceCandidate = entry.getValue();
}Object result = instanceCandidate;
//这个就是自己@Bean方法返回null 就会被处理成 NullBean 这样不需要到处判空
if (result instanceof NullBean) {if (isRequired(descriptor)) {throw new NoSuchBeanDefinitionException();}result = null;
}
//确定是否符合要求的类型 在getBean(beanName, Class)方法中
//第二个参数就是用来在这里做校验的 会判断找出的实例类型是否符合要求
if (!ClassUtils.isAssignableValue(type, result)) {throw new BeanNotOfRequiredTypeException();
}
return result;
findAutowireCandidates(beanName, requiredType, dependencyDescriptor):Map<String, Object> result = CollectionUtils.newLinkedHashMap(candidateNames.length);
//根据类型获取所有的 候选名称
//其实就是 beanFactory中
//遍历 this.beanDefinitionNames 获取name mergedBeanDefinitions.get(beanName) 获取 RootBeanDefinition
//其中 RootBeanDefinition 是用来提前判断是否符合要求
//matchFound = isTypeMatch(beanName, requiredType, true);
//matchFound == trur 就符合要求
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());判断 resolvableDependencies 中是否有符合requiredType的对象遍历 candidateNames {if(不是自依赖 && isAutowireCandidate(candidate, descriptor)) {candidates.put(candidateName, getSingleton(beanName, false).getClass());}
}
//再次判断一下是否符合要求
isAutowireCandidate(candidate, descriptor) :if (候选Definition.isAutowireCandidate() && 依赖的类型是一个 class) {match = true; //检查 @Qualifier 没配置返回truematch = checkQualifiers(bdHolder, dependencyType.getType() instanceof Class);if (match)) {//检查 @Qualifier 没配置返回truematch = checkQualifiers(bdHolder, descriptor.getMethodAnnotations();}return match;
}
//在查找到多个 候选实例的时候 确定一个最终的实例
determineAutowireCandidate(matchingBeans, descriptor):String primaryBeanName;
//解析 @Primary
遍历 candidates : candidateBeanName{if (有@Primary) {if (primaryBeanName != null) {throw new NoUniqueBeanDefinitionException();}primaryBeanName = candidateBeanName;}
}
if (primaryBeanName != null) {return primaryBeanName;
}
//解析 @javax.annotation.Priority
Integer highestPriority = null;
遍历 candidates : candidateBeanName{if (有@javax.annotation.Priority) {Integer thisPriority = Priority(value);if (thisPriority == null) {continue;}if (highestPriority != null && highestPriority == thisPriority ) {throw new NoUniqueBeanDefinitionException();}if (highestPriority == null || highestPriority < thisPriority ) {highestPriority = thisPriority;primaryBeanName = candidateBeanName;}}
}
if (primaryBeanName != null) {return primaryBeanName;
}遍历 candidates : candidateBeanName{//这个缓存里有4个可直接解析的实例//{Class@501}"interface org.springframework.core.io.ResourceLoader" -> {AnnotationConfigApplicationContext@1779}//{Class@508}"interface org.springframework.context.ApplicationEventPublisher" -> {AnnotationConfigApplicationContext@1779}//{Class@510}"interface org.springframework.context.ApplicationContext" -> {AnnotationConfigApplicationContext@1779}//{Class@504}"interface org.springframework.beans.factory.BeanFactory" -> {DefaultListableBeanFactory@1475}if (this.resolvableDependencies.containsValue(beanInstance)) {return candidateName;}//这里就是判断字段或者参数名称 是否和BeanName一致if (matchesBeanName(candidateName, descriptor.getDependencyName()) {return candidateName;}
}
相关文章:
003-依赖注入、属性赋值源码分析
目录 引入作用代码分析InstantiationAwareBeanPostProcessor#postProcessProperties()AutowiredAnnotationBeanPostProcessor查找注入点元数据给注入点注入属性 引入 之前我们了解到BeanDefinition到Bean,经历了 实例化属性赋值初始化 3个步骤现在详细分析下属性赋…...
Elasticsearch 商业启示
上月的“红帽事件”,说明开源软件的“客服模式”行不通,那么,开源软件如何赚钱呢?既不能卖软件,又不能卖支持服务,该怎么办呢?我现在的看法是,只剩下一种模式是可行的,开…...
C++/Qt 读写文件
之前写过两篇跟文件操作相关的博客,有兴趣也可以看一下: C语言读写文件 Qt关于文件路径的处理 先讲一些关于基础文本文件和二进制文件的读写操作,后续将会整理C/Qt关于ini、xml、json、xlsx相关文件的读写操作。 C 相比于C语言使用FILE文…...
linux服务器之-nethogs命令
文章目录 NetHogs 工具安装安装依赖包安装epel源安装Nethogs 使用 NetHogs 工具 NetHogs是一个小型的net top工具,不像大多数工具那样拖慢每个协议或者是每个子网的速度而是依照进程进行带宽分组。 安装 安装依赖包 yum install libpcap libpcap-devel epel-rel…...
《每天5分钟玩转kubernetes》读书笔记
笔记 概念 Pod是脆弱的,但应用是健壮的。 kubelet运行在Cluster所有节点上,负责启动Pod和容器。kubeadm用于初始化Cluster。kubectl是k8s命令行工具。通过kubectl可以部署和管理应用,查看各种资源,创建、删除和更新各种组件。 …...
【RabbitMQ】golang客户端教程4——路由(使用direct交换器)
路由 在上一教程中,我们构建了一个简单的日志记录系统。我们能够向许多接收者广播日志消息。 在本教程中,我们将向它添加一个特性-我们将使它能够只订阅消息的一个子集。例如,我们将只能将关键错误消息定向到日志文件(以节省磁盘…...
Shell脚本学习-for循环结构2
案例:通过脚本实现仅sshd、rsyslog、crond、network、sysstat服务在开机时自启动。 Linux系统在开机的服务通常工作在文本模式3级别,因此只需要查找3级别以上的开启的服务即可。查看命令: chkconfig --list |grep 3:on [rootvm1 ~]# chkco…...
vue 老项目 npm install 报错Python,c++等相关错误
老项目npm install 下载依赖包报错 解决方法: //下载python 1、 npm install --global --production windows-build-tools//配置环境 : 也可暂时不用配置,能用就不用配置(npm config set python "D:\Python27\python.exe&q…...
【c语言初级】c++基础
文章目录 1. C关键字2. 命名空间2.1 命名空间定义2.2 命名空间使用 3. C输入&输出4. 缺省参数4.1 缺省参数概念4.2 缺省参数分类 5. 函数重载5.2 C函数重载的原理--名字修饰采用C语言编译器编译后结果 1. C关键字 C是在C的基础之上,容纳进去了面向对象编程思想…...
idea打开传统eclipse项目
打开传统web项目 1.打开后选择项目文件 2.选择项目结构 3.设置jdk版本 4.导入当前项目模块 5.选择eclipse 6. 设置保存目录 7.右键模块,添加spring和web文件 8. 设置web目录之类的,并且创建打包工具 9.如果有本地lib,添加为库 最后点击应用&…...
全国各城市-财政收入-一般预算收入-各项税收-个人所得税(1999-2020年)
个人所得税是一项反映国家财政状况和个人经济水平的重要数据。通过对全国各城市个人所得税数据的研究,可以提供研究者参考的有益信息。首先,个人所得税数据反映了不同城市居民的收入水平。通过对不同城市的个人所得税数据进行比较,可以了解不…...
【动态网页抓取】 :用Python抓取所有内容的指南
一、说明 您在抓取动态网页内容时是否得到了糟糕的结果?不仅仅是你。对于标准抓取工具来说,爬网动态数据是一项具有挑战性的任务(至少可以说)。这是因为当发出HTTP请求时,响应程序的某些部分JavaScript在后台运行&…...
Spring Boot数据访问基础知识与JDBC简单实现
目录 Spring Boot数据访问基础知识 Spring Data ORM JDBC JPA JDBC简单实现 步骤1:新建Maven项目,添加依赖 步骤2:配置数据源—让程序可以访问到 步骤3:配置数据源—让IDEA可以访问到 步骤4:添加数据库和表 …...
ubuntu添加万能头文件
ubuntu的C头文件目录为/usr/include 在/usr/include下新建文件夹 bits sudo mkdir bits进入bits,新建stdc.h,并修改权限为744/777 cd bits;sudo touch stdc.h;sudo chmod 777 stdc.h将以下内容粘贴到stdc.h,保存退出 // C includes used …...
聊一聊关于前端语法 ?? 的那些事
当我们在编写前端代码时,语法是非常重要的。正确的语法可以确保我们的代码能够正常运行,并且易于维护和理解。在本文中,我们将探讨一些前端语法的问题,例如空值合并运算符(??)。 空值合并运算符是ES2020…...
宝塔Linux面板升级“获取更新包失败”怎么解决?
宝塔Linux面板执行升级命令后失败,提示“获取更新包失败,请稍后更新或联系宝塔运维”如何解决?新手站长分享宝塔面板升级失败的解决方法: 宝塔面板升级失败解决方法 1、使用root账户登录到你的云服务器上,宝塔Linux面…...
训练强化学习的经验回放策略:experience replay
经验回放:Experience Replay(训练DQN的一种策略) 优点:可以重复利用离线经验数据;连续的经验具有相关性,经验回放可以在离线经验BUFFER随机抽样,减少相关性; 超参数:Rep…...
uniapp学习
1 简单的表单校验 <!--uniapp:参考模板和字段生成页面 字段stuNumber 输入框 学号stuName 输入框 学生姓名teacher 输入框 辅导员submitDate 日期选择 填报日期morningTemperature 输入框(数字校验一位小数) 早上体温noonTemperature 输入框&…...
机器学习深度学习——数值稳定性和模型化参数(详细数学推导)
👨🎓作者简介:一位即将上大四,正专攻机器学习的保研er 🌌上期文章:机器学习&&深度学习——Dropout 📚订阅专栏:机器学习&&深度学习 希望文章对你们有所帮助 这一部…...
layui 整合UEditor 百度编辑器
layui 整合UEditor 百度编辑器 第一步:下载百度编辑器并配置好路径 百度编辑器下载地址:http://fex.baidu.com/ueditor/ 第二步:引入百度编辑器 代码如下: <div class"layui-form-item layui-form-text"><…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...
Python竞赛环境搭建全攻略
Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型(算法、数据分析、机器学习等)不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...
大数据治理的常见方式
大数据治理的常见方式 大数据治理是确保数据质量、安全性和可用性的系统性方法,以下是几种常见的治理方式: 1. 数据质量管理 核心方法: 数据校验:建立数据校验规则(格式、范围、一致性等)数据清洗&…...
webpack面试题
面试题:webpack介绍和简单使用 一、webpack(模块化打包工具)1. webpack是把项目当作一个整体,通过给定的一个主文件,webpack将从这个主文件开始找到你项目当中的所有依赖文件,使用loaders来处理它们&#x…...
电脑桌面太单调,用Python写一个桌面小宠物应用。
下面是一个使用Python创建的简单桌面小宠物应用。这个小宠物会在桌面上游荡,可以响应鼠标点击,并且有简单的动画效果。 import tkinter as tk import random import time from PIL import Image, ImageTk import os import sysclass DesktopPet:def __i…...
