当前位置: 首页 > news >正文

003-依赖注入、属性赋值源码分析

目录

    • 引入
    • 作用
    • 代码分析
    • InstantiationAwareBeanPostProcessor#postProcessProperties()
      • AutowiredAnnotationBeanPostProcessor
        • 查找注入点元数据
        • 给注入点注入属性

引入

之前我们了解到BeanDefinition到Bean,经历了

  1. 实例化
  2. 属性赋值
  3. 初始化
    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设置了

  1. Autowired.class
  2. Value.class
  3. 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&#xff0c;经历了 实例化属性赋值初始化 3个步骤现在详细分析下属性赋…...

Elasticsearch 商业启示

上月的“红帽事件”&#xff0c;说明开源软件的“客服模式”行不通&#xff0c;那么&#xff0c;开源软件如何赚钱呢&#xff1f;既不能卖软件&#xff0c;又不能卖支持服务&#xff0c;该怎么办呢&#xff1f;我现在的看法是&#xff0c;只剩下一种模式是可行的&#xff0c;开…...

C++/Qt 读写文件

之前写过两篇跟文件操作相关的博客&#xff0c;有兴趣也可以看一下&#xff1a; C语言读写文件 Qt关于文件路径的处理 先讲一些关于基础文本文件和二进制文件的读写操作&#xff0c;后续将会整理C/Qt关于ini、xml、json、xlsx相关文件的读写操作。 C 相比于C语言使用FILE文…...

linux服务器之-nethogs命令

文章目录 NetHogs 工具安装安装依赖包安装epel源安装Nethogs 使用 NetHogs 工具 NetHogs是一个小型的net top工具&#xff0c;不像大多数工具那样拖慢每个协议或者是每个子网的速度而是依照进程进行带宽分组。 安装 安装依赖包 yum install libpcap libpcap-devel epel-rel…...

《每天5分钟玩转kubernetes》读书笔记

笔记 概念 Pod是脆弱的&#xff0c;但应用是健壮的。 kubelet运行在Cluster所有节点上&#xff0c;负责启动Pod和容器。kubeadm用于初始化Cluster。kubectl是k8s命令行工具。通过kubectl可以部署和管理应用&#xff0c;查看各种资源&#xff0c;创建、删除和更新各种组件。 …...

【RabbitMQ】golang客户端教程4——路由(使用direct交换器)

路由 在上一教程中&#xff0c;我们构建了一个简单的日志记录系统。我们能够向许多接收者广播日志消息。 在本教程中&#xff0c;我们将向它添加一个特性-我们将使它能够只订阅消息的一个子集。例如&#xff0c;我们将只能将关键错误消息定向到日志文件&#xff08;以节省磁盘…...

Shell脚本学习-for循环结构2

案例&#xff1a;通过脚本实现仅sshd、rsyslog、crond、network、sysstat服务在开机时自启动。 Linux系统在开机的服务通常工作在文本模式3级别&#xff0c;因此只需要查找3级别以上的开启的服务即可。查看命令&#xff1a; chkconfig --list |grep 3:on [rootvm1 ~]# chkco…...

vue 老项目 npm install 报错Python,c++等相关错误

​​​ 老项目npm install 下载依赖包报错 解决方法&#xff1a; //下载python 1、 npm install --global --production windows-build-tools//配置环境 &#xff1a; 也可暂时不用配置,能用就不用配置&#xff08;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的基础之上&#xff0c;容纳进去了面向对象编程思想…...

idea打开传统eclipse项目

打开传统web项目 1.打开后选择项目文件 2.选择项目结构 3.设置jdk版本 4.导入当前项目模块 5.选择eclipse 6. 设置保存目录 7.右键模块&#xff0c;添加spring和web文件 8. 设置web目录之类的&#xff0c;并且创建打包工具 9.如果有本地lib&#xff0c;添加为库 最后点击应用&…...

全国各城市-财政收入-一般预算收入-各项税收-个人所得税(1999-2020年)

个人所得税是一项反映国家财政状况和个人经济水平的重要数据。通过对全国各城市个人所得税数据的研究&#xff0c;可以提供研究者参考的有益信息。首先&#xff0c;个人所得税数据反映了不同城市居民的收入水平。通过对不同城市的个人所得税数据进行比较&#xff0c;可以了解不…...

【动态网页抓取】 :用Python抓取所有内容的指南

一、说明 您在抓取动态网页内容时是否得到了糟糕的结果&#xff1f;不仅仅是你。对于标准抓取工具来说&#xff0c;爬网动态数据是一项具有挑战性的任务&#xff08;至少可以说&#xff09;。这是因为当发出HTTP请求时&#xff0c;响应程序的某些部分JavaScript在后台运行&…...

Spring Boot数据访问基础知识与JDBC简单实现

目录 Spring Boot数据访问基础知识 Spring Data ORM JDBC JPA JDBC简单实现 步骤1&#xff1a;新建Maven项目&#xff0c;添加依赖 步骤2&#xff1a;配置数据源—让程序可以访问到 步骤3&#xff1a;配置数据源—让IDEA可以访问到 步骤4&#xff1a;添加数据库和表 …...

ubuntu添加万能头文件

ubuntu的C头文件目录为/usr/include 在/usr/include下新建文件夹 bits sudo mkdir bits进入bits&#xff0c;新建stdc.h&#xff0c;并修改权限为744/777 cd bits;sudo touch stdc.h;sudo chmod 777 stdc.h将以下内容粘贴到stdc.h&#xff0c;保存退出 // C includes used …...

聊一聊关于前端语法 ?? 的那些事

当我们在编写前端代码时&#xff0c;语法是非常重要的。正确的语法可以确保我们的代码能够正常运行&#xff0c;并且易于维护和理解。在本文中&#xff0c;我们将探讨一些前端语法的问题&#xff0c;例如空值合并运算符&#xff08;??&#xff09;。 空值合并运算符是ES2020…...

宝塔Linux面板升级“获取更新包失败”怎么解决?

宝塔Linux面板执行升级命令后失败&#xff0c;提示“获取更新包失败&#xff0c;请稍后更新或联系宝塔运维”如何解决&#xff1f;新手站长分享宝塔面板升级失败的解决方法&#xff1a; 宝塔面板升级失败解决方法 1、使用root账户登录到你的云服务器上&#xff0c;宝塔Linux面…...

训练强化学习的经验回放策略:experience replay

经验回放&#xff1a;Experience Replay&#xff08;训练DQN的一种策略&#xff09; 优点&#xff1a;可以重复利用离线经验数据&#xff1b;连续的经验具有相关性&#xff0c;经验回放可以在离线经验BUFFER随机抽样&#xff0c;减少相关性&#xff1b; 超参数&#xff1a;Rep…...

uniapp学习

1 简单的表单校验 <!--uniapp:参考模板和字段生成页面 字段stuNumber 输入框 学号stuName 输入框 学生姓名teacher 输入框 辅导员submitDate 日期选择 填报日期morningTemperature 输入框&#xff08;数字校验一位小数&#xff09; 早上体温noonTemperature 输入框&…...

机器学习深度学习——数值稳定性和模型化参数(详细数学推导)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——Dropout &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你们有所帮助 这一部…...

layui 整合UEditor 百度编辑器

layui 整合UEditor 百度编辑器 第一步&#xff1a;下载百度编辑器并配置好路径 百度编辑器下载地址&#xff1a;http://fex.baidu.com/ueditor/ 第二步&#xff1a;引入百度编辑器 代码如下&#xff1a; <div class"layui-form-item layui-form-text"><…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

快刀集(1): 一刀斩断视频片头广告

一刀流&#xff1a;用一个简单脚本&#xff0c;秒杀视频片头广告&#xff0c;还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农&#xff0c;平时写代码之余看看电影、补补片&#xff0c;是再正常不过的事。 电影嘛&#xff0c;要沉浸&#xff0c;…...

系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文通过代码驱动的方式&#xff0c;系统讲解PyTorch核心概念和实战技巧&#xff0c;涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...

加密通信 + 行为分析:运营商行业安全防御体系重构

在数字经济蓬勃发展的时代&#xff0c;运营商作为信息通信网络的核心枢纽&#xff0c;承载着海量用户数据与关键业务传输&#xff0c;其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级&#xff0c;传统安全防护体系逐渐暴露出局限性&a…...

二维FDTD算法仿真

二维FDTD算法仿真&#xff0c;并带完全匹配层&#xff0c;输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...

AD学习(3)

1 PCB封装元素组成及简单的PCB封装创建 封装的组成部分&#xff1a; &#xff08;1&#xff09;PCB焊盘&#xff1a;表层的铜 &#xff0c;top层的铜 &#xff08;2&#xff09;管脚序号&#xff1a;用来关联原理图中的管脚的序号&#xff0c;原理图的序号需要和PCB封装一一…...

Python常用模块:time、os、shutil与flask初探

一、Flask初探 & PyCharm终端配置 目的: 快速搭建小型Web服务器以提供数据。 工具: 第三方Web框架 Flask (需 pip install flask 安装)。 安装 Flask: 建议: 使用 PyCharm 内置的 Terminal (模拟命令行) 进行安装,避免频繁切换。 PyCharm Terminal 配置建议: 打开 Py…...

深入解析 ReentrantLock:原理、公平锁与非公平锁的较量

ReentrantLock 是 Java 中 java.util.concurrent.locks 包下的一个重要类,用于实现线程同步,支持可重入性,并且可以选择公平锁或非公平锁的实现方式。下面将详细介绍 ReentrantLock 的实现原理以及公平锁和非公平锁的区别。 ReentrantLock 实现原理 基本架构 ReentrantLo…...