为了能够更好的说明nacos-spring-project的设计原理,我们将首先介绍一下在Spring框架中是如何管理系统属性以及用户的配置属性的。本文将从一个项目中常用的属性使用例子入手,简要的分析属性解析注入的过程,在后续的文章中将从Environment的角度分析Spring是如何进行全局的属性管理的(# Spring配置属性管理(二)— Environment)
@Value注解
@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来表示。
InjectionMetadata的解析主要是通过buildAutowiringMetadata函数利用反射来对Bean Class中的Field以及Method进行解析,要被注入的属性被封装成AutowiredFieldElement,要被注入的方法(方法参数上带有注解)被封装成AutowiredMethodElement。
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当中的。
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
if (this.cached) {
try {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
catch (NoSuchBeanDefinitionException ex) {
// Unexpected removal of target bean for cached argument -> re-resolve
value = resolveFieldValue(field, bean, beanName);
}
}
else {
value = resolveFieldValue(field, bean, beanName);
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
在InjectionElement的inject函数中利用resolveFieldValue来解析出Field当前的值,然后同样是利用反射机制将其注入到Bean中。在resolveFieldValue最终会调用DefaultListableBeanFactory的doResolveDependency来完成属性的解析。在doResolveDependency函数中首先会调用resolveEmbeddedValue来对@Value注解上的value属性进行解析,如果解析出来是SpEL的表达式的话会利用evaluateBeanDefinitionString函数进行二次解析。
@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()));
}
}
//.....
}
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包中关于属性加载与解析的部分。