Spring配置属性管理(一)-Value注解解析

本文最后更新于:2022年9月23日 上午

为了能够更好的说明nacos-spring-project的设计原理,我们将首先介绍一下在Spring框架中是如何管理系统属性以及用户的配置属性的。本文将从一个项目中常用的属性使用例子入手,简要的分析属性解析注入的过程,在后续的文章中将从Environment的角度分析Spring是如何进行全局的属性管理的(# Spring配置属性管理(二)— Environment)

@Value注解

1
2
3
4
5
6
7
@Service
public class Test {

@Value("${test}")
private String test;

}

在项目中,如上面的代码片段所示,我们通常会在Service Bean中利用@Value注解来注入配置文件(例如application.properties)中的某些自定义配置属性,这些属性实际上都是由Spring Environment负责进行统一管理与解析的,而由AutowiredAnnotationBeanPostProcessor负责在Bean中对@Value注解进行解析注入属性的。

AutowiredAnnotationBeanPostProcessor实现了SmartInstantiationAwareBeanPostProcessor以及MergedBeanDefinitionPostProcessor接口,在程序初始化时主要完成了两件事:

  • 在postProcessMergedBeanDefinition接口中解析每个Bean的BeanDefinition,查找Bean中所有被定义的@Value以及@Autowired(本文不作细致分析),并解析成InjectionMetadata
  • 在postProcessProperties接口中找到Bean以及对应属性的InjectionMetadata,由InjectionMetadata来负责对PropertyValues进行注入

postProcessMergedBeanDefinition

在postProcessMergedBeanDefinition中最重要的任务就是从BeanDefinition中构造出InjectionMetadata,InjectionMetadata顾名思义即表示了每个Bean注入的元信息。AutowiredAnnotationBeanPostProcessor中的injectionMetadataCache缓存了所有Bean的InjectionMetadata,而InjectionMetadata中每个需要被注入的点都用一个InjectedElement来表示。

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
# org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.buildAutowiringMetadata

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}

List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;

do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

ReflectionUtils.doWithLocalFields(targetClass, field -> {
//获取Field上的@Value或@Autowired注解
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) {
//忽略静态属性
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static fields: " + field);
}
return;
}
//判断属性上的required参数
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
}
});

ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static methods: " + method);
}
return;
}
if (method.getParameterCount() == 0) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation should only be used on methods with parameters: " +
method);
}
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});

elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);

return InjectionMetadata.forElements(elements, clazz);
}

postProcessProperties

在postProcessProperties函数中首先会根据当前的Bean找到解析过的InjectionMetadata,然后利用InjectionMetadata的inject函数完成注入,inject函数中会遍历所有的InjectionElement并调用其Inject方法来完成每个注入点的注入,这里我们以AutowiredFieldElement为例,重点看一下配置属性是如解析并注入到Bean当中的。

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
# org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement.inject

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
else {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
if (!this.cached) {
if (value != null || this.required) {
this.cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
this.cachedFieldValue = new ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
}
}
}
else {
this.cachedFieldValue = null;
}
this.cached = true;
}
}
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}

在InjectionElement的inject函数中利用resolveFieldValue来解析出Field当前的值,然后同样是利用反射机制将其注入到Bean中。在resolveFieldValue最终会调用DefaultListableBeanFactory的doResolveDependency来完成属性的解析。

在doResolveDependency函数中首先会调用 resolveEmbeddedValue 来对@Value注解上的value属性进行解析,如果解析出来是SpEL的表达式的话会利用evaluateBeanDefinitionString函数进行二次解析。

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency

@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}

Class<?> type = descriptor.getDependencyType();
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(strVal, bd);
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
try {
return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
}
catch (UnsupportedOperationException ex) {
// A custom TypeConverter which does not support TypeDescriptor resolution...
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
}

Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
return multipleBeans;
}

Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
return null;
}

String autowiredBeanName;
Object instanceCandidate;

if (matchingBeans.size() > 1) {
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
}
else {
// In case of an optional Collection/Map, silently ignore a non-unique case:
// possibly it was meant to be an empty collection of multiple regular beans
// (before 4.3 in particular when we didn't even look for collection beans).
return null;
}
}
instanceCandidate = matchingBeans.get(autowiredBeanName);
}
else {
// We have exactly one match.
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
autowiredBeanName = entry.getKey();
instanceCandidate = entry.getValue();
}

if (autowiredBeanNames != null) {
autowiredBeanNames.add(autowiredBeanName);
}
if (instanceCandidate instanceof Class) {
instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}
Object result = instanceCandidate;
if (result instanceof NullBean) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
result = null;
}
if (!ClassUtils.isAssignableValue(type, result)) {
throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
}
return result;
}
finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}

我们这里重点关注的是 resolveEmbeddedValue,在这个函数中会遍历所有注册StringValueResolver来对Value注解中的值进行解析。那么StringValueResolver又是在什么地方被添加到BeanFactory中的呢,通过引用关系查找我们不难发现添加StringValueResolver的地方有两处,一处是在PlaceholderConfigurerSupport中,一处则是在AbstractApplicationContext的BeanFactory初始化结束的函数中。

当AbstractApplicationContext中没有注册StringValueResolver时,才会注入一个默认的StringValueResolver,而这个默认的StringValueResolver则是利用Environment来完成属性的解析(strVal -> getEnvironment().resolvePlaceholders(strVal))。

而在Spring Boot项目中通常会自动配置一个PropertySourcesPlaceholderConfigurer的Bean来协助解析占位符,这个Bean的一方面提供了占位符的解析,另一方面对Environment进行了二次封装,加入了用户可配置的自定义属性解析,使得属性解析的数据源更加的丰富。

实际上这两个对于属性的解析的StringValueResolver最终利用的都是 PropertySourcesPropertyResolver,PropertySourcesPropertyResolver在resolvePlaceholders函数中主要经历两个步骤,首先利用PropertyPlaceholderHelper解析出字符串中所有的占位符(例如${test}),然后使用PropertySourcesPropertyResolver中getPropertyAsRawString解析出占位符中应该被替换的属性(即查找属性源中test属性)进行替换,最后返回该值。

PropertyPlaceholderHelper是通过遍历字符串的方式递归的解析所有的占位符,逻辑相对比较简单就不做深入的分析。getPropertyAsRawString函数最终的属性查找利用的是PropertySourcesPropertyResolver中的PropertySources属性,PropertySources中包含了多个PropertySource,每个PropertySource就代表一个数据配置源,可以是系统环境变量、JVM变量、配置文件或是自定义配置的本地变量(PropertySourcesPropertyResolver中提供的功能)等等。根据前面的分析我们可以知道,PropertySourcesPropertyResolver在Spring框架的代码中有两处实例化,一个是ApplicationContext在创建Enviroment时创建的默认的PropertySourcesPropertyResolver,其中的PropertySources由Enviroment提供,一个是PropertySourcesPlaceholderConfigurer创建的PropertySourcesPropertyResolver,其中的PropertySources由Enviroment与自定义的本地属性合并而成。

经过上述过程的跟踪分析,其实我们不难发现,在Spring框架中ApplicationContext中的Environment是Spring默认的属性源管理器,每个属性源都会对应一个PropertySource,属性的获取与解析是最终是通过PropertySourcesPropertyResolver来完成的。想要自定义属性源可以有两种实现方式,一是配置PropertySourcesPlaceholderConfigurer,二是通过Environment来注入新的属性源。

最后,附上一张关于@Value注解属性解析中关键函数的调用时序图,在下一篇文章中我们将会从Environment的角度来分析Spring-core中org.springframework.core.env包中关于属性加载与解析的部分。

References


Spring配置属性管理(一)-Value注解解析
https://baymax55.github.io/2022/09/23/spring/Spring配置属性管理— @Value注解解析/
作者
baymax55
发布于
2022年9月23日
许可协议