springboot中@Value的工作原理说明

 更新时间:2021年07月06日 11:50:59   作者:spring-hz  
这篇文章主要介绍了springboot中@Value的工作原理,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

我们知道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();
}

总结

@Value注解标注的bean属性装配是依靠AutowiredAnnotationBeanPostProcessor在bean的实例化、初始化阶段完成的。以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Spring 自动装配的二义性实例解析

    Spring 自动装配的二义性实例解析

    这篇文章主要介绍了Spring 自动装配的二义性实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Java 8跳过本次循环,继续执行以及跳出循环,终止循环的代码实例

    Java 8跳过本次循环,继续执行以及跳出循环,终止循环的代码实例

    今天小编就为大家分享一篇关于Java 8跳过本次循环,继续执行以及跳出循环,终止循环的代码实例,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-10-10
  • Java 后端开发中Tomcat服务器运行不了的五种解决方案

    Java 后端开发中Tomcat服务器运行不了的五种解决方案

    tomcat是在使用Java编程语言开发服务端技术使用最广泛的服务器之一,但经常在开发项目的时候会出现运行不了的情况,这里总结出几种能解决的办法
    2021-10-10
  • JAVA实现异步调用实例代码

    JAVA实现异步调用实例代码

    在java平台,实现异步调用的角色主要三种角色:调用者、取货凭证、真实数据。本篇文章给大家介绍java实现异步调用实例代码,需要的朋友可以参考下
    2015-09-09
  • SpringBoot中实现@Scheduled动态定时任务

    SpringBoot中实现@Scheduled动态定时任务

    SpringBoot中的@Scheduled注解为定时任务提供了一种很简单的实现,本文主要介绍了SpringBoot中实现@Scheduled动态定时任务,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • 浅谈Java对象禁止使用基本类型

    浅谈Java对象禁止使用基本类型

    本文主要介绍了浅谈Java对象禁止使用基本类型,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • 使用@TableField(updateStrategy=FieldStrategy.IGNORED)遇到的坑记录

    使用@TableField(updateStrategy=FieldStrategy.IGNORED)遇到的坑记录

    这篇文章主要介绍了使用@TableField(updateStrategy=FieldStrategy.IGNORED)遇到的坑及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • Maven项目部署到服务器设置访问路径以及配置虚拟目录的方法

    Maven项目部署到服务器设置访问路径以及配置虚拟目录的方法

    今天小编就为大家分享一篇关于Maven项目部署到服务器设置访问路径以及配置虚拟目录的方法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-02-02
  • Java 超详细讲解设计模式之原型模式讲解

    Java 超详细讲解设计模式之原型模式讲解

    原型模式是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式,今天通过本文给大家介绍下Java 原型设计模式,感兴趣的朋友一起看看吧
    2022-03-03
  • SpringBoot各种注解详解

    SpringBoot各种注解详解

    SpringBoot的一个核心功能是IOC,就是将Bean初始化加载到容器中,Bean是如何加载到容器的,可以使用SpringBoot注解方式或者Spring XML配置方式。SpringBoot注解方式减少了配置文件内容,更加便于管理,并且使用注解可以大大提高了开发效率
    2022-12-12

最新评论