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的实例化、初始化阶段完成的。以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。