本文最后更新于: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) List<InjectionMetadata.InjectedElement> elements = new ArrayList<>(); Class <?> targetClass = clazz; do return; } boolean required = determineRequiredStatus(ann); currElements.add(new AutowiredFieldElement(field, required)); } }); ReflectionUtils.doWithLocalMethods(targetClass, method -> MergedAnnotation <?> ann = findAutowiredAnnotation (bridgedMethod) ; if (ann != null && method .equals(ClassUtils.getMostSpecificMethod(method , clazz ))) return ; } if (method .getParameterCount() == 0 ) } 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) { 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 { return null ; } } instanceCandidate = matchingBeans.get (autowiredBeanName); } else { 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