springboot中@Value的⼯作原理说明
我们知道springboot中的Bean组件的成员变量(属性)如果加上了@Value注解,可以从有效的配置属性资源中到配置项进⾏绑定,那么这⼀切是怎么发⽣的呢?
下⽂将简要分析⼀下@Value的⼯作原理。
springboot版本: springboot-2.0.6.RELEASE
概述
spring启动流程面试回答
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 = "${}"
*
*/
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 = "${}"为例,endIndex=13
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
// 获取{}⾥的真正属性名称,此例为""
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
// 获取属性键placeholder对应的属性值
String propVal = solvePlaceholder(placeholder);
/
/ 此处逻辑是当=${bi:li}时,最终被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 = solvePlaceholder(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);
// 将${}替换为li
if (logger.isTraceEnabled()) {
}
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 + "\"");
}
}
else {
startIndex = -1;
}
}
String();
}
总结
@Value注解标注的bean属性装配是依靠AutowiredAnnotationBeanPostProcessor在bean的实例化、初始化阶段完成的。以上为个⼈经验,希望能给⼤家⼀个参考,也希望⼤家多多⽀持。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。