springboot中@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;
}
}
}
}
...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
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);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
PropertyPlaceholderHelper#parseStringValue解析属性值
/** * 一个参数示例 value = "${company.ceo}" * */ protected String parseStringValue( String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder result <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// this.placeholderPrefix = "${"</span> <span class="token keyword">int</span> startIndex <span class="token operator">=</span> value<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>placeholderPrefix<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>startIndex <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 占位符的结束位置,以value = "${company.ceo}"为例,endIndex=13</span> <span class="token keyword">int</span> endIndex <span class="token operator">=</span> <span class="token function">findPlaceholderEndIndex</span><span class="token punctuation">(</span>result<span class="token punctuation">,</span> startIndex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>endIndex <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 获取{}里的真正属性名称,此例为"company.ceo"</span> String placeholder <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token function">substring</span><span class="token punctuation">(</span>startIndex <span class="token operator">+</span> <span class="token keyword">this</span><span class="token punctuation">.</span>placeholderPrefix<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> endIndex<span class="token punctuation">)</span><span class="token punctuation">;</span> String originalPlaceholder <span class="token operator">=</span> placeholder<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>visitedPlaceholders<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>originalPlaceholder<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span> <span class="token string">"Circular placeholder reference '"</span> <span class="token operator">+</span> originalPlaceholder <span class="token operator">+</span> <span class="token string">"' in property definitions"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Recursive invocation, parsing placeholders contained in the placeholder key.</span> <span class="token comment">// 递归调用本方法,因为属性键中可能仍然有占位符</span> placeholder <span class="token operator">=</span> <span class="token function">parseStringValue</span><span class="token punctuation">(</span>placeholder<span class="token punctuation">,</span> placeholderResolver<span class="token punctuation">,</span> visitedPlaceholders<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Now obtain the value for the fully resolved key...</span> <span class="token comment">// 获取属性键placeholder对应的属性值</span> String propVal <span class="token operator">=</span> placeholderResolver<span class="token punctuation">.</span><span class="token function">resolvePlaceholder</span><span class="token punctuation">(</span>placeholder<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 此处逻辑是当company.ceo=${bi:li}时,company.ceo最终被li所替代的原因</span> <span class="token comment">// 所以配置文件中,最好不要出现类似${}的东西,因为它本身就会被spring框架所解析</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>propVal <span class="token operator">==</span> null <span class="token operator">&&</span> <span class="token keyword">this</span><span class="token punctuation">.</span>valueSeparator <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> separatorIndex <span class="token operator">=</span> placeholder<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>valueSeparator<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>separatorIndex <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> String actualPlaceholder <span class="token operator">=</span> placeholder<span class="token punctuation">.</span><span class="token function">substring</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> separatorIndex<span class="token punctuation">)</span><span class="token punctuation">;</span> String defaultValue <span class="token operator">=</span> placeholder<span class="token punctuation">.</span><span class="token function">substring</span><span class="token punctuation">(</span>separatorIndex <span class="token operator">+</span> <span class="token keyword">this</span><span class="token punctuation">.</span>valueSeparator<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> propVal <span class="token operator">=</span> placeholderResolver<span class="token punctuation">.</span><span class="token function">resolvePlaceholder</span><span class="token punctuation">(</span>actualPlaceholder<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>propVal <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> propVal <span class="token operator">=</span> defaultValue<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>propVal <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Recursive invocation, parsing placeholders contained in the</span> <span class="token comment">// previously resolved placeholder value.</span> propVal <span class="token operator">=</span> <span class="token function">parseStringValue</span><span class="token punctuation">(</span>propVal<span class="token punctuation">,</span> placeholderResolver<span class="token punctuation">,</span> visitedPlaceholders<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 将${company.ceo}替换为li</span> result<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span>startIndex<span class="token punctuation">,</span> endIndex <span class="token operator">+</span> <span class="token keyword">this</span><span class="token punctuation">.</span>placeholderSuffix<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> propVal<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>logger<span class="token punctuation">.</span><span class="token function">isTraceEnabled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> logger<span class="token punctuation">.</span><span class="token function">trace</span><span class="token punctuation">(</span><span class="token string">"Resolved placeholder '"</span> <span class="token operator">+</span> placeholder <span class="token operator">+</span> <span class="token string">"'"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> startIndex <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>placeholderPrefix<span class="token punctuation">,</span> startIndex <span class="token operator">+</span> propVal<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>ignoreUnresolvablePlaceholders<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Proceed with unprocessed value.</span> startIndex <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>placeholderPrefix<span class="token punctuation">,</span> endIndex <span class="token operator">+</span> <span class="token keyword">this</span><span class="token punctuation">.</span>placeholderSuffix<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token string">"Could not resolve placeholder '"</span> <span class="token operator">+</span> placeholder <span class="token operator">+</span> <span class="token string">"'"</span> <span class="token operator">+</span> <span class="token string">" in value \""</span> <span class="token operator">+</span> value <span class="token operator">+</span> <span class="token string">"\""</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> visitedPlaceholders<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>originalPlaceholder<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> startIndex <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> result<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
总结
@Value
注解标注的bean属性装配是依靠AutowiredAnnotationBeanPostProcessor
在bean的实例化、初始化阶段完成的。
</div>
<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-7b4cdcb592.css" rel="stylesheet">
</div>
</article>