【Spring源码分析】从源码角度去熟悉依赖注入(一)

从源码角度去熟悉依赖注入

  • 一、全局出发引出各种依赖注入策略
  • 二、@Autowired依赖注入源码分析
    • 属性注入源码分析(AutowiredFieldElement.inject)
    • 方法注入源码分析(AutowiredMethodElement.inject)
    • 流程图

其实在上篇阐述非懒加载单例Bean的实例化逻辑的时候,就有阐述过 AbstractAutowireCapableBeanFactory#createBean 的大概,它其实就阐述了一个 Bean 的生命周期:

  1. 加载BeanClass;
  2. 实例化前;
  3. 实例化;
  4. 后置处理合并后的 BeanDefinition,MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition(这不属于Bean的生命周期,但担任着很重要的角色)
  5. 实例化后;
  6. 属性填充,这里的属性填充其实就是俺们说的依赖注入。
  7. 初始化前、初始化、初始化后。
  8. 硬要加上个结尾的话,就还有个销毁,一般用不着。

先阐述一下,就当上期博客的复习咯。
这期博客呢我们从源码的角度去看看属性填充(依赖注入)的内部实现。
首先阐明一下,像源码分析这种类型的博客呢,要么是自己熟悉原理,要么是自己看过,不然看起来会觉得好困难。

一、全局出发引出各种依赖注入策略

从上篇博客中的流程也可以知道属性填充在处理 MergedBeanDefinitionPostProcessor 之后:

			// 合并后的BeanDefinition的在处理applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);// 属性填充populateBean(beanName, mbd, instanceWrapper);

接下来咱从 populateBean() 全局点的角度去引入各种属性注入的方式,下面是该方法的源码:

	@SuppressWarnings("deprecation")  // for postProcessPropertyValuesprotected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the// state of the bean before properties are set. This can be used, for example,// to support styles of field injection.// 实例化之后,属性设置之前// Spring 只是提供了这个生命周期阶段,但是实际上没有做任何处理if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {return;}}}// 这是看 BeanDefinition 中是否已经有填充的属性了PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);// Spring 自己提供的填充策略int resolvedAutowireMode = mbd.getResolvedAutowireMode();if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {// MutablePropertyValues是PropertyValues具体的实现类// 可以看见这里的 newPvs 和上面配置的 PropertyValues 是一起的MutablePropertyValues newPvs = new MutablePropertyValues(pvs);// Add property values based on autowire by name if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {autowireByName(beanName, mbd, bw, newPvs);}// Add property values based on autowire by type if applicable.if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {autowireByType(beanName, mbd, bw, newPvs);}pvs = newPvs;}// 下面是通过注解的方式进行注入的方式boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);PropertyDescriptor[] filteredPds = null;if (hasInstAwareBpps) {if (pvs == null) {pvs = mbd.getPropertyValues();}for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {// 这里会调用AutowiredAnnotationBeanPostProcessor的postProcessProperties()方法,会直接给对象中的属性赋值// AutowiredAnnotationBeanPostProcessor内部并不会处理pvs,直接返回了PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}}pvs = pvsToUse;}}if (needsDepCheck) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}checkDependencies(beanName, mbd, filteredPds, pvs);}// 如果当前Bean中的BeanDefinition中设置了PropertyValues,那么最终将是PropertyValues中的值,覆盖@Autowiredif (pvs != null) {applyPropertyValues(beanName, mbd, bw, pvs);}}
  • 先是处理实例化后,Spring 没实例化后的逻辑,只是提供了这个生命周期阶段;

  • 拿到 BeanDefinition 中已经有填充的属性,就是可以在 MergedBeanDefinitionPostProcessor 中去处理这个 BeanDefinition;比如下面这样自定义:

    • 在这里插入图片描述
  • 在上面拿到的基础上外加上解析是否通过 AUTOWIRE_BY_NAMEAUTOWIRE_BY_TYPE 俩种模式下的填充的属性(PropertyValues);

    • 在这里插入图片描述
  • 通过 InstantiationAwareBeanPostProcessor#postProcessProperties 去填充属性,这里Spring主要是去解析注解填充(常用);

    • 在这里插入图片描述
    • 在这里插入图片描述
  • 最后将需要填充的属性进行个直接引用,通过 InstantiationAwareBeanPostProcessor#postProcessProperties 去填充属性的时候会直接去赋值操作,而最上面俩种是由最后处理,也就是说若俩种方式都使用了,上面的那种才有效,其中上面俩种需要提供对应的 setter 方法。

了解完上面这些就可以开始真正的源码分析环节了,前俩种属性填充其实没啥好说的,这里简单提一下 By_Name 和 By_Type 方式进行的填充源码:
在这里插入图片描述

  • 先是获取到可以注入的属性名(这里面的具体实现是先拿到属性的内省对象,然后遍历去将满足条件的属性返回)
    • 在这里插入图片描述
    • 该属性有对应的set方法;
    • 没有被 excludeFilters 所排除;
    • 先前是没有填充过该属性的;
    • 属性类型不是简单类型
      • 比如下面容器中有个 Number 简单类型的Bean;
      • 在这里插入图片描述
      • 现在尝试注入到UserService中:在这里插入图片描述
      • 可以看见注入失败了在这里插入图片描述
  • 然后遍历返回的属性名,去获取对应的实例,然后放到 PropertyValues 对象中,由后续再直接去调用 setter 去填充。

它是有缺陷的,会把所有满足条件的 setter 方法都当做是去属性注入去调用,而且可以看见它是不让填充简单类型的实例对象的,所以就有了后续的注解式的方式。

二、@Autowired依赖注入源码分析

这里的源码逻辑和 InstantiationAwareBeanPostProcessor#postProcessPropertyValues 里实现的。其主要实现是在 AutowiredAnnotationBeanPostProcessor 中。
有必要去看一下该类的关系结构图:
在这里插入图片描述该 BeanPostProcessor 就一个无参构造,咱来看看,就是俺们希望看见的 @Autowired、@Value 注解。
在这里插入图片描述从上文中也清楚,是先执行的 MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition BeanDefinition 的再操作,再去进行属性注入。那我们看这个 BeanPostProcessor 源码,当然也是先去看 postProcessMergedBeanDefinition 方法。接下俩的分析如下:

	@Overridepublic void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {// 找注入点(所有被@Autowired注解了的Field或Method)InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);// 将寻找到的注入点放入到 BeanDefinition 中metadata.checkConfigMembers(beanDefinition);}

findAutowiringMetadata 源码分析:

	private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {// Fall back to class name as cache key, for backwards compatibility with custom callers.// 构造缓存 key,如果beanName不为空,就用beanName,否则用类的全限定名String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());// Quick check on the concurrent map first, with minimal locking.InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);if (InjectionMetadata.needsRefresh(metadata, clazz)) {synchronized (this.injectionMetadataCache) {metadata = this.injectionMetadataCache.get(cacheKey);if (InjectionMetadata.needsRefresh(metadata, clazz)) {if (metadata != null) {metadata.clear(pvs);}// 解析注入点并缓存metadata = buildAutowiringMetadata(clazz);// 存入本地缓存// 后续属性注入可以直接从这个缓存中获取this.injectionMetadataCache.put(cacheKey, metadata);}}}return metadata;}

也就是说咱得去看这里面的核心代码 buildAutowiringMetadata 它是如何去解析注入点的,拿到元数据然后放到本地缓存中,接下来是看源码环节:

	private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {// 如果一个Bean的类型是String...,那么则根本不需要进行依赖注入// 准确的说是 clazz 的全限定名是以 java. 开头,比如 java.lang.Stringif (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {return InjectionMetadata.EMPTY;}List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();Class<?> targetClass = clazz;do {final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();// 遍历targetClass中的所有FieldReflectionUtils.doWithLocalFields(targetClass, field -> {// field上是否存在@Autowired、@Value、@Inject中的其中一个MergedAnnotation<?> ann = findAutowiredAnnotation(field);if (ann != null) {// static filed不是注入点,不会进行自动注入if (Modifier.isStatic(field.getModifiers())) {if (logger.isInfoEnabled()) {logger.info("Autowired annotation is not supported on static fields: " + field);}return;}// 构造注入点boolean required = determineRequiredStatus(ann);currElements.add(new AutowiredFieldElement(field, required));}});// 遍历targetClass中的所有MethodReflectionUtils.doWithLocalMethods(targetClass, method -> {// 处理桥接方法Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {return;}// method上是否存在@Autowired、@Value、@Inject中的其中一个MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {// static method不是注入点,不会进行自动注入if (Modifier.isStatic(method.getModifiers())) {if (logger.isInfoEnabled()) {logger.info("Autowired annotation is not supported on static methods: " + method);}return;}// set方法最好有入参// 要是没参的话会打印日志信息,但是也会注入if (method.getParameterCount() == 0) {if (logger.isInfoEnabled()) {logger.info("Autowired annotation should only be used on methods with parameters: " +method);}}boolean required = determineRequiredStatus(ann);PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);currElements.add(new AutowiredMethodElement(method, required, pd));}});elements.addAll(0, currElements);targetClass = targetClass.getSuperclass();}while (targetClass != null && targetClass != Object.class);return InjectionMetadata.forElements(elements, clazz);}
  • 总结流程
    • 从该类开始不断遍历父类
    • 通过反射机制遍历所有的属性
      • 看属性上面是不是有 @Autowired、@Value、@Inject 中的其中一个
      • 排除属性是静态的,静态的属性类方面的成员,不属于实体级别的,不应该进行属性注入
      • 创建注入点 AutowiredFieldElement 实体对象,然后放入到 InjectedElement 集。
    • 通过反射机制遍历所有的方法
      • 其他点和上面属性是一样的,如看方法上有无那些注解,排除方法是静态的…
      • 然后创建注入点 AutowiredMethodElement 实体对象,然后放入到 InjectedElement 集。

OK,也就是通过这咱就拿到了需要注入的属性和方法集,接下来就可以进行注入咯。
现咱去看看 InstantiationAwareBeanPostProcessor#postProcessPropertyValues ,这里指的是 AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues

	@Overridepublic PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {// 找注入点(所有被@Autowired注解了的Field或Method)InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {metadata.inject(bean, beanName, pvs);} catch (BeanCreationException ex) {throw ex;} catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);}return pvs;}
  • findAutowiringMetadata 方法找到对应需要注入的元数据对象,上面分析过,这里就是从本地缓存拿到的 InjectionMetadata 对象;
  • 接下来直接进行注入~

注入 inject 方法实现其实很简单,就是遍历 InjectedElement 对象,然后依次调用它的 inject 进行遍历。
在这里插入图片描述
咱再过来看看 AutowiredFiledElement、AutowiredMethodElement、InjectedElement 之间的关系:
在这里插入图片描述
接下来就是看属性注入和方法注入是咋地实现的咯~

属性注入源码分析(AutowiredFieldElement.inject)

		@Overrideprotected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Field field = (Field) this.member;Object value;if (this.cached) {// 对于原型Bean,第一次创建的时候,也找注入点,然后进行注入,此时cached为false,注入完了之后cached为true// 第二次创建的时候,先找注入点(此时会拿到缓存好的注入点),也就是AutowiredFieldElement对象,此时cache为true,也就进到此处了// 注入点内并没有缓存被注入的具体Bean对象,而是beanName,这样就能保证注入到不同的原型Bean对象try {value = resolvedCachedArgument(beanName, this.cachedFieldValue);} catch (NoSuchBeanDefinitionException ex) {// Unexpected removal of target bean for cached argument -> re-resolvevalue = resolveFieldValue(field, bean, beanName);}} else {// 根据filed从BeanFactory中查到的匹配的Bean对象value = resolveFieldValue(field, bean, beanName);}// 反射给filed赋值if (value != null) {ReflectionUtils.makeAccessible(field);field.set(bean, value);}}
  • resolveFieldValue 方法,从 BeanFactory 中查询到对应的 Bean 对象;
  • 反射进行属性赋值

方法注入源码分析(AutowiredMethodElement.inject)

		@Overrideprotected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {// 如果pvs中已经有当前注入点的值了,则跳过注入if (checkPropertySkipping(pvs)) {return;}Method method = (Method) this.member;Object[] arguments;if (this.cached) {try {arguments = resolveCachedArguments(beanName);} catch (NoSuchBeanDefinitionException ex) {// Unexpected removal of target bean for cached argument -> re-resolvearguments = resolveMethodArguments(method, bean, beanName);}} else {arguments = resolveMethodArguments(method, bean, beanName);}if (arguments != null) {try {ReflectionUtils.makeAccessible(method);method.invoke(bean, arguments);} catch (InvocationTargetException ex) {throw ex.getTargetException();}}}
  • 排除掉 PropertyValues 已经有过的注入点,因为在上述提到过,是先进行的@Autowired 注入,然后再去 PropertyValues 的注入;(我不知道为啥属性注入那为什么不先排除,再看注不注入)
  • resolveMethodArguments 找各个参数的对象;
  • 反射调用对应的方法。

流程图

这篇已经很长了,现在只是阐述到根据注入点类型找 Bean,如何实现通过注入点类型找 Bean 还没阐述,再写下一篇博客进行阐述,都放这篇可能太长了。

在这里插入图片描述

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

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

相关文章

UE5 独立程序的网络TCP/UDP服务器与客户端基础流程

引擎源码版&#xff0c;复制\Engine\Source\Programs\路径下的BlankProgram空项目示例。 重命名BlankProgram&#xff0c;例如CustomTcpProgram&#xff0c;并修改项目名称。 修改.Build.cs内容 修改Target.cs内容 修改Private文件夹内.h.cpp文件名并修改.cpp内容 刷新引擎 …

模拟StopWatch改写的一款耗时调试工具

StopWatch在单个文件的单个方法中确时还蛮好用的&#xff0c;但跨多个文件&#xff0c;多个方法的同一线程内调试就有明显的不舒服。一是要建立ThreadLocal共享StopWatch的实例。二是StopWatch的start和stop必须形成闭合。在方法嵌套的场景。比如要查看大方法执行时间&#xff…

性能测试中的基准测试

在性能测试中有一种测试类型叫做基准测试。这篇文章&#xff0c;就聊聊关于基准测试的一些事儿。 1、定义 通过设计合理的测试方法&#xff0c;选用合适的测试工具和被测系统&#xff0c;实现对某个特定目标场景的某项性能指标进行定量的和可对比的测试。 2、特质 ①、可重…

ChatGPT 如何解决 “Something went wrong. lf this issue persists ….” 错误

Something went wrong. If this issue persists please contact us through our help center at help.openai.com. ChatGPT经常用着用着就出现 “Something went wrong” 错误&#xff0c;不管是普通账号还是Plus账号&#xff0c;不管是切换到哪个节点&#xff0c;没聊两次就报…

Unity3D代码混淆方案详解

背景 Unity引擎使用Mono运行时&#xff0c;而C#语言易受反编译影响&#xff0c;存在代码泄露风险。本文通过《QQ乐团》项目实践&#xff0c;提出一种适用于Unity引擎的代码混淆方案&#xff0c;以保护代码逻辑。 引言 在Unity引擎下&#xff0c;为了防止代码被轻易反编译&a…

Vue created()和 activated()区别和作用调用顺序

目录 作用 区别 举个例子 调用顺序 作用 简单说都是用于页面初始化&#xff0c;土话说一进来页面&#xff0c;去进行数据加载或调用方法的 区别 首先activated 钩子只适用于被 <keep-alive> 包裹的组件。对于不被 <keep-alive> 包裹的组件&#xff0c;activate…

CSS实现的 Loading 效果

方式一、纯CSS实现 代码&#xff1a;根据需要复制 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>CSS Animation Library for Developers and Ninjas</title><style>/* ---------------…

2024年甘肃省职业院校技能大赛信息安全管理与评估 样题三 模块一

竞赛需要完成三个阶段的任务&#xff0c;分别完成三个模块&#xff0c;总分共计 1000分。三个模块内容和分值分别是&#xff1a; 1.第一阶段&#xff1a;模块一 网络平台搭建与设备安全防护&#xff08;180 分钟&#xff0c;300 分&#xff09;。 2.第二阶段&#xff1a;模块二…

ILI2130触控IC驱动

概述 ili2130触控网上有关的介绍很少&#xff0c;官网也没有有用的东西&#xff0c;所以记录一其驱动&#xff08;MCU驱动&#xff09;。此外ILI2520, ILI2521, ILI2322, ILI2323, ILI2316, ILI2326, ILI2130, ILI2131, ILI2132这几款触控IC使用的是同一个用户指导手册&#x…

吃瓜教程Task1:概览西瓜书+南瓜书第1、2章

由于本人之前已经学习过西瓜书&#xff0c;本次学习主要是对以往知识的查漏补缺&#xff0c;因此本博客记录了在学习西瓜书中容易混淆的点以及学习过程中的难点。更多学习内容可以参考下面的链接&#xff1a; 南瓜书的地址&#xff1a;https://github.com/datawhalechina/pumpk…

zabbix实验

目录 一、zabbix 自动发现与自动注册 1、zabbix 自动发现 ①关闭防火墙和安全机制 ②在服务端和客户端上配置 hosts 解析 ③在 Web 页面配置自动发现 2、zabbix 自动注册 ①环境准备 ②在服务端和客户端上配置 hosts 解析 ③修改 zabbix-agent2 配置文件 ④在 Web 页…

从静态到动态:视频美颜SDK在短视频平台的应用全面解析

为了在短视频平台上实现更出色的美颜效果&#xff0c;开发者们通常会借助视频美颜SDK。本文将深入探讨视频美颜SDK在短视频平台上的应用&#xff0c;从技术原理、性能优化到用户体验等方面进行全面解析。 一、技术原理与算法演进 视频美颜SDK的核心在于其算法&#xff0c;而…

企业网络扫描程序中需要的功能

网络扫描程序已成为每个 IT 管理员抵御安全漏洞的第一道防线不可或缺的一部分。使用正确的网络扫描程序工具进行有效的网络侦察和诊断&#xff0c;使管理员能够查明可能升级为安全风险和网络事故的网络问题。典型的网络扫描程序可以与 IP 扫描程序配合使用&#xff0c;按顺序扫…

Failed to start OpenSSH server daemon-SSH启动失败

一、SSH服务启动失败 或者报错误&#xff1a; journalctl -xe sshd.service 二、查看SSHD的服务状态 3、重新安装openssh [rootzbx ~]# yum -y remove openssh 卸载原来的 [rootzbx ~]# yum -y install openssh openssh-clients openssh-server 重新安装 [rootzbx ~]# system…

用冒泡排序谈默认参数应用

前面在调用函数提到为了将信息打印到ofil中&#xff0c;前面提到的办法是 ofstream ofil("text_out1"); void bubble_sort(vector<int> vec){ } 在file scope中定义ofil&#xff0c;这是一个不受欢迎的举动。这样比较难在其他环境重用 一般的程序编写法则是&…

学习Redux:React状态管理的简明指南

学习Redux&#xff1a;React状态管理的简明指南 一、Redux介绍 Redux 是React最常用的集中状态管理工具&#xff0c;类似于Vue中的Pinia&#xff08;Vuex&#xff09;&#xff0c;可以独立于框架运行 作用&#xff1a;通过集中管理的方式管理应用的状态 为什么要使用Redux&…

linux基础学习(4):rpm包与相关命令

1.linux内的软件包分类 linux只有2种软件包&#xff1a;源码包 与 二进制包 &#xff08;1&#xff09;源码包 源码包就是开源的源程序包&#xff0c;使用者可以直接看到其程序&#xff0c;也可以进行修改 &#xff08;2&#xff09;二进制包 由于源码包安装难度大&#x…

【k8s】Kubernetes技术和相关命令简介

一、 Kubernetes简介 Kubernetes是Google开源的一个容器编排引擎&#xff0c;它支持自动化部署、大规模可伸缩、应用容器化管理。在生产环境中部署一个应用程序时&#xff0c;通常要部署该应用的多个实例以便对应用请求进行负载均衡。kubernetes&#xff0c;简称K8s&#xff0…

C# 读取ini文件示例

一般使用一个相关win32 api的封装类&#xff1b;我用的如下&#xff1b; using System; using System.Runtime.InteropServices; using System.Text;namespace DotNet.Utilities {/// <summary>/// INI文件读写类。/// </summary>public class INIFile{public str…

【总结】Linux命令中文帮助手册

1. 为什么要总结Linux命令中文帮助手册 Linux 官方并不提供中文的 help、man 帮助手册。网络上已有的前人翻译过的中文手册版本比较老&#xff0c;且翻译存在误差。从记忆角度来看&#xff0c;Linux 很多命令都不一定记得住详细的用法&#xff0c;易遗忘&#xff0c;缺少经验总…