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

766 阅读2分钟

为了能够更好的说明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包中关于属性加载与解析的部分。

InjectionMetadata_inject.png