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

Spring @Value注解的依赖注入实现原理

Spring @Value注解的依赖注入实现原理

    • 一,什么是Value注解的依赖注入
    • 二,实现原理
    • 三,代码实现
      • 1. 定义 @Value 注解
      • 2. 实现 InstantiationAwareBeanPostProcessor
      • 3. 实现 AutowiredAnnotationBeanPostProcessor
      • 4. 占位符解析逻辑
      • 5. 定义 StringValueResolver 接口
      • 6. 实现 PlaceholderResolvingStringValueResolver
      • 7. 注册解析器
      • 8. 集成到 BeanFactory

源码见:mini-spring

在这里插入图片描述

一,什么是Value注解的依赖注入

在 Spring 框架中,@Value 注解是一种常用的依赖注入方式,用于直接为 Bean 的属性注入值。以下是一个简单的示例:

@Value("jixu")
private String name;

除了直接注入静态值,@Value 还支持属性占位符,能够从配置文件(如 application.propertiesapplication.yml)中动态读取值。例如:

@Value("${sex}")
private String sex;

通过这种方式,我们可以灵活地将外部配置的值注入到 Bean 的属性中。

二,实现原理

要理解 @Value 注解的工作原理,首先需要明确其作用的时机和位置。显然,@Value 的功能需要在 Bean 属性赋值操作之前完成。具体来说,我们需要修改 BeanDefinition,为其添加对应的 PropertyValue,从而确保在 Bean 实例化并执行属性赋值时,能够通过 set 方法正确注入值。

位置已经确定了,那么再具体一点需要依赖的组建也就是BeanPostProcess,再精确一点是InstantiationAwareBeanPostProcessor,也就是我们在实现AOP融入Bean生命周期的时候定义的接口,该接口是用于处理实例化的相关操作,因此该扩展功能也会在这里实现。我们通之前的操作逻辑一样会定义一个抽象方法用于属性赋值,之后在我们具体的实现类当中实现相关逻辑,再加入 到对应的BeanPostProcess容器当中,之后在AbstractAutowireCapableBeanFactory的对应位置进行处理即可。

  1. 定义抽象方法:与 Spring 中其他扩展逻辑类似,我们可以先定义一个抽象方法,用于处理属性的赋值逻辑。
  2. 实现具体逻辑:在具体的实现类中,解析 @Value 注解,提取注解中的值(或占位符),并将其转换为 PropertyValue,附加到 BeanDefinition 上。
  3. 注册到容器:将实现的 InstantiationAwareBeanPostProcessor 加入到 Spring 的 BeanPostProcessor 容器中。
  4. 集成到生命周期:Spring 会在 AbstractAutowireCapableBeanFactory 的适当位置调用我们的处理器,完成 @Value 注解的处理。

三,代码实现

1. 定义 @Value 注解

首先,定义自定义的 @Value 注解:

@Target({ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
public @interface Value {  String value();  
}

2. 实现 InstantiationAwareBeanPostProcessor

在 InstantiationAwareBeanPostProcessor 中添加用于处理 PropertyValues 的方法:

PropertyValues postProcessPropertyValues(PropertyValues propertyValues , Object bean , String beanName);

3. 实现 AutowiredAnnotationBeanPostProcessor

定义 AutowiredAnnotationBeanPostProcessor 类,实现 InstantiationAwareBeanPostProcessor 接口,并在 postProcessPropertyValues 方法中实现具体的解析逻辑:

  
@Component  
public class AutowiredAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor , BeanFactoryAware {  private ConfigurableBeanFactory beanFactory;  @Override  public PropertyValues postProcessPropertyValues(PropertyValues propertyValues, Object bean, String beanName) {  Class<?> beanClass = bean.getClass();  PropertyValues pvs = new PropertyValues();  // 获取到当前类当中声明的所有属性  Field[] declaredFields = beanClass.getDeclaredFields();  for (Field field : declaredFields) {  // 获取到标记Value注解的属性  Value valueAnnotation = field.getAnnotation(Value.class);  if (valueAnnotation != null){  String value = valueAnnotation.value();  // 解析Value的属性值,判断是否需要替换占位符  value = beanFactory.resolveEmbeddedValue(value);  // 将解析完毕的字段添加到类属性当中  // BeanUtil.setFieldValue(bean,field.getName(),value);  pvs.addPropertyValue(new PropertyValue(field.getName(), value));  }  }  return pvs;  }  @Override  public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {  return null;  }  /**  * 在 Bean 初始化之前执行自定义处理逻辑。  * 使用此方法,可以在 Bean 被初始化之前对其进行修改或执行其他操作。  *  * @param bean     当前正在初始化的 Bean 实例。  * @param beanName 当前 Bean 的名称。  * @return 返回处理后的 Bean 实例,可以是原始 Bean 或修改后的 Bean。  */  @Override  public Object postProcessBeforeInitialization(Object bean, String beanName) {  return null;  }  /**  * 在 Bean 初始化之后执行自定义处理逻辑。  * 使用此方法,可以在 Bean 初始化完成后对其进行进一步的修改或执行其他操作。  *  * @param bean     当前已经初始化的 Bean 实例。  * @param beanName 当前 Bean 的名称。  * @return 返回处理后的 Bean 实例,可以是原始 Bean 或修改后的 Bean。  */  @Override  public Object postProcessAfterInitialization(Object bean, String beanName) {  return null;  }  @Override  public void setBeanFactory(BeanFactory beanFactory) {  this.beanFactory = (ConfigurableBeanFactory) beanFactory;  }  
}

说明:postProcessPropertyValues 方法接收的 propertyValues 参数包含当前 Bean 的所有属性值,但在此处未充分利用(例如未检查属性重复)。方法通过反射获取字段,检查 @Value 注解,解析值后添加到一个新的 PropertyValues 对象中返回。相比官方实现(使用 MutablePropertyValues 合并原始 propertyValues),此实现进行了简化。

4. 占位符解析逻辑

在上述逻辑中,resolveEmbeddedValue 方法用于解析占位符。定义如下:

// 在 ConfigurableBeanFactory 中定义
String resolveEmbeddedValue(String value);/*** 添加属性解析器,以便解析嵌入值中的占位符* @param stringValueResolver 属性解析器*/
void addEmbeddedValueResolver(StringValueResolver stringValueResolver);

由 AbstractBeanFactory 实现:

/**  * 解析嵌入值,用于Value注解解析  *  * @param value * @return */@Override  
public String resolveEmbeddedValue(String value) {  String result = value;  for (StringValueResolver resolver : embeddedValueResolvers) {  // 会判断传入字段是否包含属性占位符,如果包含则替换为配置文件当中的值  result = resolver.resolveStringValue(result);  }  return result;  }  /**  * 添加属性解析器  *  * @param stringValueResolver */@Override  
public void addEmbeddedValueResolver(StringValueResolver stringValueResolver) {  embeddedValueResolvers.add(stringValueResolver);  
}

疑问解答:addEmbeddedValueResolver 的作用在于支持多个配置文件。Spring 可能配置多个 PropertyPlaceholderConfigurer,每个对应一个配置文件。通过添加多个 StringValueResolver,可以依次解析占位符,确保所有配置文件的属性值都被正确替换。

5. 定义 StringValueResolver 接口

定义工具类接口以抽象占位符解析逻辑:

public interface StringValueResolver {  String resolveStringValue(String strVal);  }

6. 实现 PlaceholderResolvingStringValueResolver

在 PropertyPlaceholderConfigurer 中定义内部类实现该接口:

定义一个内部类PlaceholderResolvingStringValueResolver实现该工具类的方法,这样就为所有的PropertyPlaceholderConfigurer提供了一层抽象层的实现,用于解析占位符。

/**  * 定义字符解析器  */  
public class PlaceholderResolvingStringValueResolver implements StringValueResolver {  // 配置文件Properties对象  private final Properties properties;  public PlaceholderResolvingStringValueResolver(Properties properties) {  this.properties = properties;  }  @Override  public String resolveStringValue(String strVal) {  return PropertyPlaceholderConfigurer.this.resolverPlaceholder(strVal,properties);  }  
}

7. 注册解析器

在 PropertyPlaceholderConfigurer 的 postProcessBeanFactory 方法中注册解析器:

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {  // 加载属性配置文件  Properties properties = loadProperties();  // 属性值替换占位符  processProperties(beanFactory, properties);  // 添加属性解析器  StringValueResolver resolver = new PlaceholderResolvingStringValueResolver(properties);  beanFactory.addEmbeddedValueResolver(resolver);  
}

说明:postProcessBeanFactory 在容器刷新时执行,扫描所有 BeanFactoryPostProcessor 实现(如 PropertyPlaceholderConfigurer)。每个 PropertyPlaceholderConfigurer 对应一个配置文件,确保多配置文件场景下的占位符解析。

8. 集成到 BeanFactory

在 AbstractAutowireCapableBeanFactory 的 doCreateBean 方法中,实例化后、赋值前执行:

// 通过InstantiationStrategy实例化Bean  
bean = createBeanInstance(beanDefinition);  applyBeanPostprocessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition);  // 为Bean的属性进行赋值  
applyPropertyValues(bean , beanDefinition , beanName);

具体实现逻辑如下:

private void applyBeanPostprocessorsBeforeApplyingPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) {  List<BeanPostProcessor> beanPostProcessors = getBeanPostProcessors();  for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {  if (beanPostProcessor instanceof  InstantiationAwareBeanPostProcessor) {  PropertyValues propertyValues = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessPropertyValues(beanDefinition.getPropertyValues(), bean, beanName);  if (propertyValues != null) {  for (PropertyValue propertyValue : propertyValues.getPropertyValueList()) {  beanDefinition.getPropertyValues().addPropertyValue(propertyValue);  }  }  }  }  
}

说明:该逻辑与 AOP 融入生命周期的实现类似,通过循环调用 BeanPostProcessor,处理并合并 PropertyValues。

相关文章:

Spring @Value注解的依赖注入实现原理

Spring Value注解的依赖注入实现原理 一&#xff0c;什么是Value注解的依赖注入二&#xff0c;实现原理三&#xff0c;代码实现1. 定义 Value 注解2. 实现 InstantiationAwareBeanPostProcessor3. 实现 AutowiredAnnotationBeanPostProcessor4. 占位符解析逻辑5. 定义 StringVa…...

三、kafka消费的全流程

五、多线程安全问题 1、多线程安全的定义 使用多线程访问一个资源&#xff0c;这个资源始终都能表现出正确的行为。 不被运行的环境影响、多线程可以交替访问、不需要任何额外的同步和协同。 2、Java实现多线程安全生产者 这里只是模拟多线程环境下使用生产者发送消息&…...

商品模块中的多规格设计:实现方式与电商/ERP系统的架构对比

在商品管理系统中&#xff0c;多规格设计&#xff08;Multi-Specification Product Design&#xff09;是一个至关重要但又极具挑战性的领域。无论是面向消费者的电商系统&#xff0c;还是面向企业管理的ERP系统&#xff0c;对商品规格的处理方式直接影响库存管理、订单履约、数…...

(三)动手学线性神经网络:从数学原理到代码实现

1 线性回归 线性回归是一种基本的预测模型&#xff0c;用于根据输入特征预测连续的输出值。它是机器学习和深度学习中最简单的模型之一&#xff0c;但却是理解更复杂模型的基础。 1.1 线性回归的基本元素 概念理解&#xff1a; 线性回归假设输入特征和输出之间存在线性关系。…...

Axure形状类组件图标库(共8套)

点击下载《月下倚楼图标库(形状组件)》 原型效果&#xff1a;https://axhub.im/ax9/02043f78e1b4386f/#g1 摘要 本图标库集锦精心汇集了8套专为Axure设计的形状类图标资源&#xff0c;旨在为产品经理、UI/UX设计师以及开发人员提供丰富多样的设计素材&#xff0c;提升原型设计…...

20250530-C#知识:String与StringBuilder

String与StringBuilder string字符串在开发中经常被用到&#xff0c;不过在需要频繁对字符串进行增加和删除时&#xff0c;使用StringBuilder有利于提升效率。 1、String string是一种引用类型而非值类型&#xff08;某些方面像值类型&#xff09;使用“”进行两个string对象的…...

从 Docker 到 Containerd:Kubernetes 容器运行时迁移实战指南

一、背景 Kubernetes 自 v1.24 起移除了 dockershim&#xff0c;不再原生支持 Docker Engine&#xff0c;用户需迁移至受支持的 CRI 兼容运行时&#xff0c;如&#xff1a; Containerd&#xff08;推荐&#xff0c;高性能、轻量级&#xff09; CRI-O&#xff08;专为 Kuberne…...

uniapp中view标签使用范围

不止用于微信小程序。兼容型号&#xff0c;是uniapp内置组件之一&#xff0c;在uniapp中进行了跨平台适配。支持所有uniapp的平台。如微信小程序、h5、app、支付宝小程序...

Celery 核心概念详解及示例

Celery 核心概念详解及示例 Celery 是一个简单、灵活且可靠的分布式系统&#xff0c;用于处理大量消息&#xff0c;提供对任务队列的操作&#xff0c;并支持任务的调度和异步执行。它常用于深度优化 Web 应用的性能和响应速度&#xff0c;通过将耗时的操作移到后台异步执行&am…...

欢乐熊大话蓝牙知识14:用 STM32 或 EFR32 实现 BLE 通信模块:从0到蓝牙,你也能搞!

&#x1f680; 用 STM32 或 EFR32 实现 BLE 通信模块&#xff1a;从0到蓝牙&#xff0c;你也能搞&#xff01; “我能不能自己用 STM32 或 EFR32 实现一个 BLE 模块&#xff1f;” 答案当然是&#xff1a;能&#xff01;还能很帅&#xff01; &#x1f468;‍&#x1f3ed; 前…...

IDEA 在公司内网配置gitlab

赋值项目链接 HTTPS 将HTTP的链接 ip地址换成 内网地址 例如&#xff1a;https:172.16.100.18/...... 如果出现需要需要Token验证的情况&#xff1a; 参考&#xff1a;Idea2024中拉取代码时GitLab提示输入token的问题_gitlab token-CSDN博客...

黑马Java面试笔记之 微服务篇(业务)

一. 限流 你们项目中有没有做过限流?怎么做的? 为什么要限流呢? 一是并发的确大(突发流量) 二是防止用户恶意刷接口 限流的实现方式: Tomcat:可以设置最大连接数 可以通过maxThreads设置最大Tomcat连接数,实现限流,但是适用于单体架构 Nginx:漏桶算法网关,令牌桶算法自定…...

通过WiFi无线连接小米手机摄像头到电脑的方法

通过WiFi无线连接小米手机摄像头到电脑的方法 以下是基于Scrcpy和DroidCam两种工具的无线连接方案&#xff0c;需提前完成开发者模式与USB调试的开启&#xff08;参考原教程步骤&#xff09;&#xff1a; 方法一&#xff1a;Scrcpy无线投屏&#xff08;无需手机端安装&#xf…...

长短期记忆(LSTM)网络模型

一、概述 长短期记忆&#xff08;Long Short-Term Memory&#xff0c;LSTM&#xff09;网络是一种特殊的循环神经网络&#xff08;RNN&#xff09;&#xff0c;专门设计用于解决传统 RNN 在处理长序列数据时面临的梯度消失 / 爆炸问题&#xff0c;能够有效捕捉长距离依赖关系。…...

深入理解 Linux 文件系统与日志文件分析

一、Linux 文件系统概述 1. 文件系统的基本概念 文件系统&#xff08;File System&#xff09;是操作系统用于管理和组织存储设备上数据的机制。它提供了一种结构&#xff0c;使得用户和应用程序能够方便地存储和访问数据。 2. Linux 文件系统结构 Linux 文件系统采用树状目…...

CSS3美化页面元素

1. 字体 <span>标签 字体样式⭐ 字体类型&#xff08;font-family&#xff09; 字体大小&#xff08;font-size&#xff09; 字体风格&#xff08;font-style&#xff09; 字体粗细&#xff08;font-weight&#xff09; 字体属性&#xff08;font&#xff09; 2. 文本 文…...

网络安全-等级保护(等保)3-0 等级保护测评要求现行技术标准

################################################################################ 第三章&#xff1a;测评要求、测评机构要求&#xff0c;最终目的是通过测评&#xff0c;所以我们将等保要求和测评相关要求一一对应形成表格。 GB/T 28448-2019 《信息安全技术 网络安全等…...

WPS 利用 宏 脚本拆分 Excel 多行文本到多行

文章目录 WPS 利用 宏 脚本拆分 Excel 多行文本到多行效果需求背景&#x1f6e0; 操作步骤代码实现代码详解使用场景注意事项总结 WPS 利用 宏 脚本拆分 Excel 多行文本到多行 在 Excel 工作表中&#xff0c;我们经常遇到一列中包含多行文本&#xff08;用换行符分隔&#xff…...

R语言错误处理方法大全

在R语言的批量运行中&#xff0c;常需要自动跳过错误&#xff0c;继续向下运行。 1、使用 tryCatch() 捕获错误并返回占位符 # 示例&#xff1a;循环中跳过错误继续执行 results <- numeric(5) # 预分配结果向量for(i in 1:5) {# 用 tryCatch 包裹可能出错的代码results[…...

AI“实体化”革命:具身智能如何重构体育、工业与未来生活

近年来&#xff0c;人工智能&#xff08;AI&#xff09;技术的飞速发展正在重塑各行各业&#xff0c;而具身智能&#xff08;Embodied AI&#xff09;作为AI领域的重要分支&#xff0c;正逐渐从实验室走向现实应用。具身智能的核心在于让AI系统具备物理实体&#xff0c;能够与环…...

Opencv4 c++ 自用笔记 05 形态学操作

图像形态学主要获取物体的形状与位置信息。利用具有一定形态的结构元素度量和提取图像中的对应形状&#xff0c;达到对图像分析和识别的目的。操作主要包括腐蚀、膨胀、开运算和闭运算。 像素距离与连通域 图像形态学中&#xff0c;将不与其他区域链接的独立区域称为集合或者…...

DrissionPage 数据提取技巧全解析:从入门到实战

在当今数据驱动的时代&#xff0c;网页数据提取已成为自动化办公、市场分析和爬虫开发的核心技能。作为新一代网页自动化工具&#xff0c;DrissionPage 以其独特的双模式融合设计&#xff08;Selenium Requests&#xff09;脱颖而出。本文将结合官方文档与实战案例&#xff0c…...

如何构建自适应架构的镜像

目标 我有一个服务叫xxx&#xff0c;一开始它运行在x86架构的机器上&#xff0c;所以最开始有个xxx:stable-amd64的镜像&#xff0c;后来它又需要运行在arm64架构的机器上&#xff0c;所以又重新打了个xxx:stable-arm64的镜像 但是对于安装脚本来说&#xff0c;我不希望我在拉…...

R语言基础| 创建数据集

在R语言中&#xff0c;有多种数据类型&#xff0c;用以存储和处理数据。每种数据类型都有其特定的用途和操作函数&#xff0c;使得R语言在处理各种数据分析任务时非常灵活和强大&#xff1a; 向量&#xff08;Vector&#xff09;: 向量是R语言中最基本的数据类型&#xff0c;它…...

剑指offer15_数值的整数次方

数值的整数次方 实现函数 double Power(double base, int exponent) 题目要求 计算 base exponent \text{base}^{\text{exponent}} baseexponent&#xff1a; 不得使用库函数不需要考虑大数问题&#xff0c;绝对误差不超过 10 − 2 10^{-2} 10−2不会出现底数和指数同为 0…...

Centos7搭建zabbix6.0

此方法适用于zabbix6以上版本zabbix6.0前期环境准备&#xff1a;Lamp&#xff08;linux httpd mysql8.0 php&#xff09;mysql官网下载位置&#xff1a;https://dev.mysql.com/downloads/mysql/Zabbix源码包地址&#xff1a;https://www.zabbix.com/cn/download_sourcesZabbix6…...

使用Redis的四个常见问题及其解决方案

Redis 缓存穿透 定义&#xff1a;redis查询一个不存在的数据&#xff0c;导致每次都查询数据库 解决方案&#xff1a; 如果查询的数据为空&#xff0c;在redis对应的key缓存空数据&#xff0c;并设置短TTL。 因为缓存穿透通常是因为被恶意用不存在的查询参数进行压测攻击&…...

Docker 部署前后端分离项目

1.Docker 1.1 什么是 Docker &#xff1f; Docker 是一种开源的 容器化平台&#xff0c;用于开发、部署和运行应用程序。它通过 容器&#xff08;Container&#xff09; 技术&#xff0c;将应用程序及其依赖项打包在一个轻量级、可移植的环境中&#xff0c;确保应用在不同计算…...

云游戏混合架构

云游戏混合架构通过整合本地计算资源与云端能力&#xff0c;形成了灵活且高性能的技术体系&#xff0c;其核心架构及技术特征可概括如下&#xff1a; 一、混合架构的典型模式 分层混合模式‌ 前端应用部署于公有云&#xff08;如渲染流化服务&#xff09;&#xff0c;后端逻辑…...

【小红书】API接口,获取笔记核心数据

小红书笔记核心数据API接口详解 - 深圳小于科技提供专业数据服务 深圳小于科技&#xff08;官网&#xff1a;https://www.szlessthan.com&#xff09;推出的小红书笔记核心数据API接口&#xff0c;为开发者提供精准的笔记互动数据分析能力&#xff0c;助力内容运营与商业决策。…...