springboot中@Value的工作原理说明
作者:spring-hz 发布时间:2023-08-24 04:48:49
我们知道springboot中的Bean组件的成员变量(属性)如果加上了@Value注解,可以从有效的配置属性资源中找到配置项进行绑定,那么这一切是怎么发生的呢?
下文将简要分析一下@Value的工作原理。
springboot版本: springboot-2.0.6.RELEASE
概述
springboot启动过程中,有两个比较重要的过程,如下:
1 扫描,解析容器中的bean注册到beanFactory上去,就像是信息登记一样。
2 实例化、初始化这些扫描到的bean。
@Value的解析就是在第二个阶段。BeanPostProcessor定义了bean初始化前后用户可以对bean进行操作的接口方法,它的一个重要实现类AutowiredAnnotationBeanPostProcessor正如javadoc所说的那样,为bean中的@Autowired和@Value注解的注入功能提供支持。
解析流程
调用链时序图
@Value解析过程中的主要调用链,我用以下时序图来表示:
这里先简单介绍一下图上的几个类的作用。
AbstractAutowireCapableBeanFactory
: 提供了bean创建,属性填充,自动装配,初始胡。支持自动装配构造函数,属性按名称和类型装配。实现了AutowireCapableBeanFactory接口定义的createBean方法。
AutowiredAnnotationBeanPostProcessor
: 装配bean中使用注解标注的成员变量,setter方法, 任意的配置方法。比较典型的是@Autowired注解和@Value注解。
InjectionMetadata
: 类的注入元数据,可能是类的方法或属性等,在AutowiredAnnotationBeanPostProcessor类中被使用。
AutowiredFieldElement
: 是AutowiredAnnotationBeanPostProcessor的一个私有内部类,继承InjectionMetadata.InjectedElement,描述注解的字段。
StringValueResolver
: 一个定义了处置字符串值的接口,只有一个接口方法resolveStringValue,可以用来解决占位符字符串。本文中的主要实现类在PropertySourcesPlaceholderConfigurer#processProperties方法中通过lamda表达式定义的。供ConfigurableBeanFactory类使用。
PropertySourcesPropertyResolver
: 属性资源处理器,主要功能是获取PropertySources属性资源中的配置键值对。
PropertyPlaceholderHelper
: 一个工具类,用来处理带有占位符的字符串。形如${name}的字符串在该工具类的帮助下,可以被用户提供的值所替代。替代途经可能通过Properties实例或者PlaceholderResolver(内部定义的接口)。
PropertyPlaceholderConfigurerResolver
: 上一行所说的PlaceholderResolver接口的一个实现类,是PropertyPlaceholderConfigurer类的一个私有内部类。实现方法resolvePlaceholder中调用了外部类的resolvePlaceholder方法。
调用链说明
这里主要介绍一下调用链中的比较重要的方法。
AbstractAutowireCapableBeanFactory#populateBean方法用于填充bean属性,执行完后可获取属性装配后的bean。
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
...
if (hasInstAwareBpps) {
// 遍历所有InstantiationAwareBeanPostProcessor实例设置属性字段值。
for (BeanPostProcessor bp : getBeanPostProcessors()) {
// AutowiredAnnotationBeanPostProcessor会进入此分支
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
//上行代码执行后,bw.getWrappedInstance()就得到了@Value注解装配属性后的bean了
if (pvs == null) {
return;
}
}
}
}
...
}
InjectionMetadata#inject逐个装配bean的配置属性。
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Collection<InjectedElement> checkedElements = this.checkedElements;
Collection<InjectedElement> elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
// 依次注入属性
for (InjectedElement element : elementsToIterate) {
if (logger.isDebugEnabled()) {
logger.debug("Processing injected element of bean '" + beanName + "': " + element);
}
element.inject(target, beanName, pvs);
}
}
}
PropertyPlaceholderHelper#parseStringValue解析属性值
/**
* 一个参数示例 value = "${company.ceo}"
*
*/
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(value);
// this.placeholderPrefix = "${"
int startIndex = value.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
// 占位符的结束位置,以value = "${company.ceo}"为例,endIndex=13
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
// 获取{}里的真正属性名称,此例为"company.ceo"
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
// 递归调用本方法,因为属性键中可能仍然有占位符
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
// 获取属性键placeholder对应的属性值
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
// 此处逻辑是当company.ceo=${bi:li}时,company.ceo最终被li所替代的原因
// 所以配置文件中,最好不要出现类似${}的东西,因为它本身就会被spring框架所解析
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
// 将${company.ceo}替换为li
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return result.toString();
}
来源:https://blog.csdn.net/gs_albb/article/details/85401720


猜你喜欢
- 接上一篇文章:Android实现图片区域裁剪功能上一篇文章提及了通过调用系统相册或拍照来实现图片的缩放\裁剪。不过这对于笔者项目的要求同样不
- LRU是Least Recently Used 的缩写,翻译过来就是“最近最少使用”,LRU缓存就是使用这种原理实现,简单的说就是缓存一定量
- 公司的svn的地址改变了,怎么办呢。自己本地的正在修改的项目怎么办呢?修改一下svn的服务器地址咯。1.就是先关闭ide,重新打开,然后选择
- 在使用spring提供的JpaTemplate进行查询时,如果数据量超过100 条,查询效率就会明显降低。由于开始时使用JPA内部的双向关联
- 1. 异常1.1 异常概念异常,就是不正常的意思。在生活中:医生说,你的身体某个部位有异常,该部位和正常相比有点不同,该部位的功能将受影响.
- 场景描述单例模式对于我们来说一点也不模式,是一个常见的名称,单例模式在程序中的实际效果就是:确保一个程序中只有一个实例,并提供一个全局访问点
- Android Dialog 对话框 1、Dialog介绍 2、AlertDialog的基本使用 3、自定义对话框 Custom Dialo
- 本文实例讲述了Android编程开发之在Canvas中利用Path绘制基本图形的方法。分享给大家供大家参考,具体如下:在Android中绘制
- 目录前言asyncawait从以往知识推导创建异步任务创建异步任务并返回Task异步改同步说说 await Task说说 async Tas
- 寻找到application.yml的读取的操作。从spring.factories 中查看到# Application Listeners
- 本文实例讲述了spring多数据源配置实现方法。分享给大家供大家参考,具体如下:在网上找到的配置多数据源的方法。1.扩展 org.sprin
- 本文实例讲述了java设计模式之工厂模式。分享给大家供大家参考,具体如下:工厂模式(factory)涉及到4个角色:抽象工厂类角色,具体工厂
- 表达式目录树表达式目录树:语法树,或者说是一种数据结构1.表达式目录树Expression:System.Linq.Expressions;
- 什么是自动装箱,拆箱先抛出定义,Java中基础数据类型与它们的包装类进行运算时,编译器会自动帮我们进行转换,转换过程对程序员是透明的,这就是
- 前言我们在开发Web应用时,肯定要为用户提供上传的功能,比如用户上传一张图像作为头像等。为了能上传文件,我们必须将表单的method设置为P
- 比如:int (*foo)(int arg),记住要和另一个指针函数区分开来,类似这样:int *foo(int arg).比如我们可以这样
- ScrapySharp是一个帮助我们快速实现网页数据采集的库,它主要提供了如下两个功能从Url获取Html数据提供CSS选择器的方式解析Ht
- 在我们实际业务中,可能存在多个类之间相互调用,形成了一个复杂的网状结构。这时候就需要有一种模式去“捋顺&rdqu
- Java 中的引用类型:强引用、软引用、弱引用和虚引用强引用如 Object object = new Object(),那 object
- 当一个产品或者项目由大量独立模块组成时,想要从 Git 挨个下载下来导入 IDE 查看并不容易,此时可以结合使用 Git 和 Maven 的