Spring-基于xml自动装配

ops/2024/10/20 16:28:05/
xmlns="http://www.w3.org/2000/svg" style="display: none;">

版本 Spring Framework 6.0.9​

1. 定义

Spring IoC容器在无需显式定义每个依赖关系的情况下,根据指定的策略,自动为指定的bean中所依赖的类类型或接口类型属性赋值。

2. 关键配置元素

BeanDefinitionParserDelegate类定义了autowire属性的属性值,代表了不同的自动装配策略。
在这里插入图片描述

2.1 < bean>元素的autowire属性

在XML配置文件中定义时,可以为其设置autowire属性来启用自动装配。该属性可取以下值:

  • byType
    Spring容器会查找容器中与目标bean属性类型相匹配的所有bean,并将其中的一个(如果有多个同类型bean,则选择其中一个,具体选择规则取决于容器的处理逻辑)注入到该属性。
  • byName
    Spring容器尝试按属性名作为bean的ID在容器中查找相应的bean。即如果目标bean有一个名为car的属性,那么容器会查找ID为car的bean并将其注入。
  • constructor
    Spring容器会查找与bean构造器参数类型相匹配的bean,并通过构造器注入的方式创建bean实例。
  • defaultno
    禁用自动装配,default 会转换成 nono 是Spring的默认设置。所有依赖项必须通过显式配置(如或标签)来注入。

2.2 < beans>标签的default-autowire属性

在< beans>元素上可以设置default-autowire属性,用于指定整个配置文件中所有未显式指定autowire属性的的默认自动装配策略。该属性可选值与 < bean>元素的autowire属性一样(byType、byName、constructor、default、no)。

3. 原理

3.1 获取autowire的默认值

在基于xml配置的ioc容器中,spring会创建一个DocumentDefaultsDefinition对象,并通过BeanDefinitionParserDelegate#populateDefaults方法进行初始化,使用默认的 lazy-init、autowire、依赖项检查设置、init-method、destroy-method 和 merge 填充,以防未在本地显式设置默认值。

红圈部分的代码在处理Spring XML配置文件中 标签的默认自动装配属性。

  1. 从当前 < beans> 标签节点中获取名为DEFAULT_AUTOWIRE_ATTRIBUTE(default-autowire)的属性值,
  2. 如果获取到的autowire值为空或等于default,则从父级获取自动装配模式(如果有);否则,使用预定义的常量AUTOWIRE_NO_VALUE(no)作为默认值,即不进行自动装配。
  3. 最后将计算得到的autowire值(即当前标签的默认自动装配模式)设置到defaults对象中,作为默认设置的对象,以便后续处理或传递给其他方法。

这样做的目的是确保每个< beans>标签都有一个明确的默认自动装配设置,以便在解析其内部bean定义时使用。
在这里插入图片描述

  • DEFAULT_AUTOWIRE_ATTRIBUTE
    在这里插入图片描述
  • AUTOWIRE_NO_VALUE
    在这里插入图片描述
  • isDefaultValue
    在这里插入图片描述

3.2 解析autowire的属性值

红圈部分的代码在处理Spring XML配置文件中 标签的自动装配属性。

  1. 从当前 < bean> 标签节点中获取名为AUTOWIRE_ATTRIBUTE(autowire)的属性值。
  2. 用刚刚获取的属性值作为入参attrValue调用autowire方法
    • 如果attrValue为空或者default,从defaults对象中获取默认的自动装配模式字符串,并将其赋值给attr。即上面提到的默认值(一般情况下为no)。
    • 初始化整型变量autowire,将其值设为AbstractBeanDefinition.AUTOWIRE_NO,表示默认不进行自动装配。
    • 根据attr值的不同,分别设置autowire变量为不同的自动装配模式整数值(byName=1,byType=2,constructor=3,autodetect=4)。另外,autodetect模式已被标记为@Deprecated,提示用户应尽量避免使用。
  3. 最后将计算得到的autowire整型值设置到对应的BeanDefinition对象中(autowireMode属性)。

在这里插入图片描述

3.3 使用autowire的属性值

根据autowire属性值执行自动装配:

  • byType:容器遍历bean的所有setter方法(或无参构造器后有setter的字段),查找与setter参数类型相匹配的bean。找到匹配项后,调用对应的setter方法进行注入。
  • byName:容器查找bean的所有setter方法(或无参构造器后有setter的字段),根据setter方法名(去掉set前缀,将首字母小写)或字段名作为bean ID在容器中查找bean。找到匹配项后,调用setter方法或直接赋值进行注入。
  • constructor:容器分析bean的构造器参数,查找与每个参数类型相匹配的bean。找到匹配项后,通过构造器注入方式创建bean实例。

3.3.1 byType

在bean声明周期的属性填充(populateBean方法)阶段,从bean定义中获取自动装配模式(autowireMode),如果值为autowireMode=2(AUTOWIRE_BY_TYPE=2),调用autowireByType方法执行按类型装配。
在这里插入图片描述

autowireByType的方法,用于按类型自动装配给定bean的属性。获取当前bean中所有待自动装配的非简单属性名称。遍历这些属性名称,通过resolveDependency方法获取满足依赖关系的bean实例,注入到对应属性。通过该方法,Spring IoC容器能够根据bean的类型自动为其属性注入合适的依赖bean实例。
在这里插入图片描述

unsatisfiedNonSimpleProperties(mbd, bw);

unsatisfiedNonSimpleProperties的方法为了找出给定AbstractBeanDefinition和BeanWrapper对象中未被显式设置且非简单的待自动装配属性名称。条件如下:
- 具有写方法
- 未被排除在依赖检查中
- PropertyValues中未已包含该属性的名称,即该属性尚未被显式设置。
- 非简单类型

在这里插入图片描述

bw.getPropertyDescriptor(propertyName)

BeanWrapper#getPropertyDescriptor方法获取属性的描述器,如下例子,定义一个UserController类,其包含一个userService属性,在xml配置文件中配置根据类型自动装配。getPropertyDescriptor方法返回两个类型为GenericTypeAwarePropertyDescriptor的描述器。
- propertyType为class,包含读方法getClass(),没有写方法
- propertyType为userService,没有读方法,包含写方法public void org.springframework.learn.ioc.auto.UserController.setUserService(org.springframework.learn.ioc.auto.UserService)

在这里插入图片描述
在这里插入图片描述

BeanUtils.getWriteMethodParameter(pd);

根据传入的PropertyDescriptor对象类型,分别从GenericTypeAwarePropertyDescriptor子类实例或普通PropertyDescriptor实例中提取与属性关联的写方法,并封装为MethodParameter对象返回。从上面getPropertyDescriptor方法中可知,返回的是GenericTypeAwarePropertyDescriptor子类实例,提取与属性关联的写方法。即是上面案例中的setUserService方法.。在这里插入图片描述

resolveDependency(desc, beanName, autowiredBeanNames, converter);

resolveDependency方法可以分成三种解析情况

  • 特殊类型的依赖解析:Optional类型、ObjectFactory/ObjectProvider类、javax.inject.Provider类。
  • 支持延迟解析:尝试获取一个用于延迟解析依赖的代理对象
  • 常规依赖解析:调用doResolveDependency方法解析依赖(一般情况下解析出符合所需类型的名称后,调用bean工厂的getBean方法获取bean实例)

resolveDependency方法根据依赖类型的差异采用不同的解析策略,包括创建适配特定类型的依赖提供者、支持延迟解析以及进行常规依赖解析。最终,该方法返回解析后的依赖对象。
在这里插入图片描述
doResolveDependency方法作用是解析依赖项。一般情况下,逻辑可以简单划分两部分

  • 解析依赖值(descriptor包含依赖项),获取bean名称。
  • 通过bean工厂获取bean实例并返回。

具体逻辑该方法会处理多种场景以解析由DependencyDescriptor描述的依赖项,同时考虑bean名称、自动装配规则及类型转换。它根据依赖项是否成功解析返回解析后的依赖项对象或针对各种错误条件抛出异常。
在这里插入图片描述

registerDependentBean(autowiredBeanName, beanName);

registerDependentBean方法用于注册两个相互依赖的bean之间的关系(两个内部数据结构dependentBeanMap 和 dependenciesForBeanMap),比如上述案例

  • dependentBeanMap:key=依赖Bean(属性userService)
  • dependenciesForBeanMap:key=依赖Bean的依赖项(userController)

在这里插入图片描述
在这里插入图片描述

3.3.2 byName

在bean声明周期的属性填充(populateBean方法)阶段,从bean定义中获取自动装配模式(autowireMode),如果值为autowireMode=1(AUTOWIRE_BY_NAME=1),调用autowireByName方法执行按名称装配。
在这里插入图片描述
autowireByName方法则比较简单,相较于autowireByType少了类型匹配的捕捉,先判断bean工厂是否包含bean名称,若包含则按照bean名称自动装配bean的属性(getBean方法获取bean实例)。
在这里插入图片描述

3.3.3 constructor

在bean声明周期的实例化(createBeanInstance方法)阶段,从bean定义中获取自动装配模式(autowireMode),如果值为autowireMode=3(AUTOWIRE_CONSTRUCTOR=2),调用autowireConstructor方法查找与bean构造器参数类型相匹配的bean,并通过构造器注入的方式创建bean实例。

在这里插入图片描述

autowireConstructor方法创建了一个包含当前bean工厂的构造函数解析器ConstructorResolver,并调用其autowireConstructor方法,完成实际的构造函数自动装配工作…
在这里插入图片描述

ConstructorResolver#autowireConstructor方法根据给定的bean名称、bean定义、用户指定构造器(若有)和显式参数值(若有),选择合适的构造器及其参数,完成bean的实例化过程。简单的过程可以理解成确认最佳构造器和所需参数,然后完成bean实例化。
在这里插入图片描述

上面的方法是获取最佳构造器和所需参数,通过构造器实例化bean。没有自动装配的具体使用逻辑(只判断了是否自动装配)。实际上,构造器类型的自动装配体现在createArgumentArray方法中。

	argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);

步骤如下:

  1. 定义局部变量,如类型转换器converter、构造参数结果值args等。
  2. 遍历给定参数数组,从resolvedValues获取匹配的构造器参数,转换后(如果未转换)保存到结果args中。获取不到则进行自动匹配。
  3. 往bean工厂注册依赖bean关系。
  4. 返回解析后的参数结果args。
    在这里插入图片描述
例子

以下面配置为例子,看createArgumentArray方法

  • 定义一个名称为userController的UserController,有两个属性
    • name属性在配置文件的value值是一个spel表达式 #{userControllerNameUtil.getUserControllerName()},name属性的类型为String。
    • userService属性未配置,使用自动装配
  • 定义一个名称为userControllerNameUtil的UserControllerNameUtil类,用来提供name,其中getUserControllerName方法返回的是Interger类型。
  • 定义一个名称为userService的UserServiceImpl类。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

进入org.springframework.beans.factory.support.ConstructorResolver#autowireConstructor方法,获取到bean定义的构造函数参数name的原始配置值 #{userControllerNameUtil.getUserControllerName()}
在这里插入图片描述

resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);方法解析spel表达式,调用UserControllerNameUtil类的getUserControllerName方法,获取到Integer类型的 123456 解析值。
在这里插入图片描述

调用createArgumentArray方法构造参数的结果值。
在这里插入图片描述

  • 处理构造参数userService
    在这里插入图片描述
    在这里插入图片描述
  • 处理构造参数name
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

参考

  • Spring使用有参构造器创建对象autowireConstructor方法

http://www.ppmy.cn/ops/15551.html

相关文章

Python-面向对象(类的组成,特殊方法和参数,私有化)

面向对象&#xff0c;强调的是对象(实体)面向对象是一种思想&#xff0c;更加符合人的思维习惯面向对象使复杂的问题简单化了面向对象的出现&#xff0c;让曾经在过程的执行者&#xff0c;变成了对象的指挥者 一.类的组成 类可以描述世间万物&#xff0c;类都需要有类名&#…

python之schedule

在Python中&#xff0c;可以使用schedule模块来执行定时任务。这个模块提供了简单易用的API&#xff0c;可以让你按照指定的时间间隔或特定时间点执行函数或任务。 首先&#xff0c;需要使用pip安装schedule模块&#xff1a; pip install schedule下面是一个简单的例子&#…

Netbox 4 VMware OVF快速部署

介绍 为了方便大家安装部署&#xff0c;做了一个VMware虚拟机ovf镜像使用。 Netbox系列&#xff1a;https://songxwn.com/categories/NetBox/ 手动部署&#xff1a;https://songxwn.com/netbox4-CN/ 使用 使用ESXi 6.5作为基准兼容性版本&#xff0c;虚拟机配置为4C8G 80G硬…

批量控制教程-Ansible管理windows

背景 你厌恶要手动操作多台机器进行某些重复的操作吗&#xff1f;想象一下&#xff0c;在周五的晚上你想要下班了&#xff0c;但是你得在很多台机器手动发布一些东西&#xff0c;每台机器都要整半小时&#xff0c;整整8台机器&#xff0c;一晚上几个小时可以预见又没了。 ans…

jmeter-while控制器用法

condition中添加while结束循环的条件&#xff0c;以下语句的意思是&#xff0c;当percent等于100时&#xff0c;就跳出while循环继续执行 ${__javaScript("${percent}" ! 100)} 举例&#xff0c;以下方法是getPercent为一个引出的异步接口&#xff0c;该接口的返回包…

C#使用ftp进行文件上传和下载功能(一)

一.FTP概述 FTP (File Transfer Protocol&#xff0c;文件传输协议)是典型的C/S架构的应用层协议&#xff0c;需要由服务端软件、客户端软件两个部分共同实现文件传输功能。FTP客户端和服务器之间的连接是可靠的&#xff0c;面向连接的&#xff0c;为数据的传输提供了可靠的保证…

企业如何建立网络事件应急响应团队?

企业如何建立网络事件应急响应团队&#xff1f; 一、应急响应中每一秒都很重要二、选择外包服务还是建立自己的网络安全应急响应团队&#xff1f;三、您的应急响应团队需要谁&#xff1f;1. 应急响应小组2. 技术支持和监控团队 四、应急响应小组1. 安全分析工程师 – 具有网络、…

Java通过邮件发送验证码和通过手机号发送验证码

前提&#xff1a;我将验证码存入了map集合&#xff0c;进行验证。 private static HashMap<String, Integer> emailMap new HashMap<>();一、通过邮箱发送验证码&#xff1a; 1、准备条件&#xff1a;引入hutool依赖&#xff0c; <!--hutool--><depend…