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

article/2025/6/7 15:04:46

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。


http://www.hkcw.cn/article/JyeKLpKlbm.shtml

相关文章

Oracle、PostgreSQL 与 MySQL 数据库对比分析与实践指南

一、三大数据库基础认知 Oracle数据库 基本概况 ✔ 厂商&#xff1a;Oracle Corporation ✔ 许可证&#xff1a;商业授权&#xff08;含Oracle XE免费版本&#xff09; ✔ 典型用户&#xff1a;大型银行、政府机构、电信运营商 核心特性 -- 示例&#xff1a;Oracle PL/SQL存…

protobuf arena实现概述

Arena是Protobuf的C特有特性&#xff0c;旨在优化内存分配效率&#xff0c;减少频繁的堆内存申请与释放。其核心机制如下&#xff1a; 预分配内存&#xff1a;Arena预先分配一大块连续内存&#xff08;称为Block&#xff09;&#xff0c;对象创建时直接从该内存块中分配&#x…

深入浅出图神经网络:从核心概念到实战落地

文章目录 1 引言1.1 发展脉络与现状1.2 面临挑战1.3 本文目标 2 图结构数据基础2.1 关键元素2.2 数学定义与常用符号2.3 图的常见类型2.4 为什么这些定义重要&#xff1f; 3 GNN 核心思想&#xff1a;消息传递机制3.1 消息函数 M E S S A G E ( k ) \mathrm{MESSAGE}^{(k)} ME…

6级阅读学习

先找连接词&#xff0c;and什么的 再找that什么的 最后找介词短语

当 AI 超越人类:从技术突破到文明拐点的 2025-2030 年全景展望

引言:当科幻照进现实的十年 2025 年的某个清晨,当你对着智能音箱说出 “帮我订一份早餐” 时,或许不会想到,这个简单指令背后的技术演进,正悄然推动人类文明走向一个前所未有的拐点。从弱人工智能(ANI)到强人工智能(AGI)的跃迁,不再是科幻小说的专属设定,而是现实世…

安全-JAVA开发-第一天

目标&#xff1a; 安装环境 了解基础架构 了解代码执行顺序 与数据库进行连接 准备&#xff1a; 安装 下载IDEA并下载tomcat&#xff08;后续出教程&#xff09; 之后新建项目 注意点如下 1.应用程序服务器选择Web开发 2.新建Tomcat的服务器配置文件 并使用 Hello…

Spring @Autowired自动装配的实现机制

Spring Autowired自动装配的实现机制 Autowired 注解实现原理详解一、Autowired 注解定义二、Qualifier 注解辅助指定 Bean 名称三、BeanFactory&#xff1a;按类型获取 Bean四、注入逻辑实现五、小结 源码见&#xff1a;mini-spring Autowired 注解实现原理详解 Autowired 的…

【AI News | 20250603】每日AI进展

AI Repos 1、dgm 是一个创新的自改进系统&#xff0c;通过迭代修改自身代码并利用编码基准验证每次更改&#xff0c;实现开放式进化。该系统旨在提升 AI 代理的代码修改能力。DGM 支持 OpenAI 和 Anthropic API&#xff0c;依赖 Docker 环境&#xff0c;并集成了 SWE-bench 和…

Rust 学习笔记:Cargo 工作区

Rust 学习笔记&#xff1a;Cargo 工作区 Rust 学习笔记&#xff1a;Cargo 工作区创建工作区在工作区中创建第二个包依赖于工作区中的外部包向工作区添加测试将工作区中的 crate 发布到 crates.io添加 add_two crate 到工作区总结 Rust 学习笔记&#xff1a;Cargo 工作区 随着项…

操作系统 第 39 章 插叙:文件和目录

两项关键操作系统技术的发展&#xff1a;进程&#xff0c;虚拟化的 CPU&#xff1b;地址空间&#xff0c;虚拟化的内存。 这一部分加上虚拟化拼图中最关键的一块&#xff1a;持久存储。永久存储设备永久地&#xff08;或至少长时间地&#xff09;存储信息&#xff0c;如传统硬盘…

楼宇自控系统联动暖通空调:解密建筑环境舒适度提升路径

走进现代建筑&#xff0c;无论是办公场所、商业中心&#xff0c;还是医院、酒店&#xff0c;人们对环境舒适度的要求越来越高。暖通空调作为调节建筑室内环境的关键设备&#xff0c;其运行效果直接影响着人们的体验。然而&#xff0c;传统暖通空调独立运行、调控不灵活等问题&a…

Freemarker快速入门

Freemarker概述 FreeMarker 是一款 模板引擎&#xff1a; 即一种基于模板和要改变的数据&#xff0c; 并用来生成输出文本(HTML网页&#xff0c;电子邮件&#xff0c;配置文件&#xff0c;源代码等)的通用工具。 它不是面向最终用户的&#xff0c;而是一个Java类库&#xff0c…

黑盒(功能)测试基本方法

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、黑盒测试的概念 1、什么是黑盒测试 &#xff08;1&#xff09;黑盒测试又称功能测试、数据驱动测试或基于规格说明书的测试&#xff0c;是一种从用户观点出…

[java八股文][JavaSpring面试篇]SpringCloud

了解SpringCloud吗&#xff0c;说一下他和SpringBoot的区别 Spring Boot是用于构建单个Spring应用的框架&#xff0c;而Spring Cloud则是用于构建分布式系统中的微服务架构的工具&#xff0c;Spring Cloud提供了服务注册与发现、负载均衡、断路器、网关等功能。 两者可以结合…

chromedriver 下载失败

问题描述 chromedriver 2.46.0 下载失败 淘宝https://registry.npmmirror.com/chromedriver/2.46/chromedriver_win32.zip无法下载 解决方法 找到可下载源 https://cdn.npmmirror.com/binaries/chromedriver/2.46/chromedriver_win32.zip &#xff0c;先将其下载到本地目录(D…

74. 搜索二维矩阵 (力扣)

给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff0c;如果 target 在矩阵中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。…

CppCon 2014 学习:Rolling Your Own Circuit Simulator

这段话讲述了一个背景和动机&#xff0c;目的是阐明为什么开源C库变得越来越复杂且在科学和工程领域有很大的应用潜力。 关键点&#xff1a; 开源库的成熟&#xff1a; 近年来&#xff0c;开源C库在许多科学和工程领域变得越来越成熟和强大。这些库不再仅仅是简单的工具&…

无人机自主降落论文解析

Dynamic Landing of an Autonomous Quadrotor on a Moving Platform in Turbulent Wind Conditions 滑膜控制器 这一部分详细介绍了边界层滑模控制器&#xff08;Boundary Layer Sliding Controller&#xff0c;BLSC&#xff09;的设计和实现&#xff0c;特别是如何将其应用于…

.NET 原生驾驭 AI 新基建实战系列(一):向量数据库的应用与畅想

在当今数据驱动的时代&#xff0c;向量数据库&#xff08;Vector Database&#xff09;作为一种新兴的数据库技术&#xff0c;正逐渐成为软件开发领域的重要组成部分。特别是在 .NET 生态系统中&#xff0c;向量数据库的应用为开发者提供了构建智能、高效应用程序的新途径。 一…

html基础01:前端基础知识学习

html基础01&#xff1a;前端基础知识学习 1.个人建立打造 -- 之前知识的小总结1.1个人简历展示1.2简历信息填写页面 1.个人建立打造 – 之前知识的小总结 1.1个人简历展示 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8&qu…