SpringBoot 中 @Value 源码解析

1、引言

在之前的《SpringBoot 自动装配》文章中,我介绍了ConfigurationClassPostProcessor这个类,它是 SpringBoot 作为扩展 Spring 一系列功能的基础路口,它所衍生的ConfigurationClassParser作为解析职责的基本处理类,涵盖了各种解析处理的逻辑,如@Configuration@Bean@Import@ImportSource@PropertySource@ComponentScan等注解都在这个解析类中完成。由于ConfigurationClassPostProcessorBeanDefinitionRegistryPostProcessor的实现类,于是其解析时机是在AbstractApplicationContext#invokeBeanFactoryPostProcessors方法中,并且是在处理BeanFactoryPostProcessor之前。

以上的注解,都是将 bean 信息注入到 Spring 容器,那么当我们需要读取配置文件的信息时,则需要使用到@Value或者@ConfigurationProperties注解。那么接下来,我们就深入源码,了解@Value的实现机制。

2、原理

在探索它的实现原理之前,我们首先定位关键字然后反推代码逻辑。我们通过搜索 "Value.class" 进行反推:


找到了一个看起来像是调用点的地方,进入QualifierAnnotationAutowireCandidateResolver,查看其类的注释说明:


/**
 * {@link AutowireCandidateResolver} implementation that matches bean definition qualifiers
 * against {@link Qualifier qualifier annotations} on the field or parameter to be autowired.
 * Also supports suggested expression values through a {@link Value value} annotation.
 *
 * <p>Also supports JSR-330's {@link javax.inject.Qualifier} annotation, if available.
 *
 * @author Mark Fisher
 * @author Juergen Hoeller
 * @author Stephane Nicoll
 * @since 2.5
 * @see AutowireCandidateQualifier
 * @see Qualifier
 * @see Value
 */

大致的意思是,它是AutowireCandidateResolver的实现类,用于匹配在属性或者方法参数上的@Qualifier注解所需要的 bean 信息;同时支持@Value注解中的表达式解析。

于是我们可以肯定QualifierAnnotationAutowireCandidateResolver就是我们要找的处理类,它负责处理@Qualifier@Value两个注解的取值操作。接下来我们看处理@ValuegetSuggestedValue方法:

    @Override
    public Object getSuggestedValue(DependencyDescriptor descriptor) {
        // 在属性上查找注解信息
        Object value = findValue(descriptor.getAnnotations());
        if (value == null) {
            MethodParameter methodParam = descriptor.getMethodParameter();
            if (methodParam != null) {
                // 在方法属性上查找注解信息
                value = findValue(methodParam.getMethodAnnotations());
            }
        }
        return value;
    }

    /**
     * Determine a suggested value from any of the given candidate annotations.
     */
    protected Object findValue(Annotation[] annotationsToSearch) {
        if (annotationsToSearch.length > 0) {   // qualifier annotations have to be local
            // 查找 @Value 的注解信息
            AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(
                    AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType);
            if (attr != null) {
                // 返回注解中的表达式
                return extractValue(attr);
            }
        }
        return null;
    }

该方法的目的是获取@Value注解中的表达式,查找范围是在目标类的属性和方法参数上。

现在要解决两个疑问:

  1. 表达式对应的值是在哪里被替换的?
  2. 表达式替换后的值又是如何与原有的 bean 整合的?

带着这两个疑问,我们顺着调用栈继续查找线索,发现getSuggestedValue方法是被DefaultListableBeanFactory#doResolveDependency方法调用了:

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

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

            Class<?> type = descriptor.getDependencyType();
            // 获取 @Value 中的表达式
            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());
                // 转换为对应的类型,并且注入原有 bean 属性或者方法参数中
                return (descriptor.getField() != null ?
                        converter.convertIfNecessary(value, type, descriptor.getField()) :
                        converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
            }
            ...
        }
        finally {
            ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
        }
    }

了解 Spring 中getBean流程的同学应该知道,DefaultListableBeanFactory#doResolveDependency作用是处理 bean 中的依赖。由此可见,处理@Value注解的时机是在getBean方法中,即SpringApplication#run的最后一步,实例化 bean。

当获取@Value注解中的表达式之后,进入了resolveEmbeddedValue方法,来替换表达式的值:

public String resolveEmbeddedValue(String value) {
        if (value == null) {
            return null;
        }
        String result = value;
        // 遍历 StringValueResolver
        for (StringValueResolver resolver : this.embeddedValueResolvers) {
            result = resolver.resolveStringValue(result);
            if (result == null) {
                return null;
            }
        }
        return result;
    }

通过代码逻辑我们看到,对于属性的解析已经委托给了StringValueResolver对应的实现类,接下来我们就要分析一下这个StringValueResolver是如何初始化的。

2.1 初始化 StringValueResolver

StringValueResolver功能实现依赖 Spring 的切入点是PropertySourcesPlaceholderConfigurer,我们看一下它的结构。


它的关键是实现了BeanFactoryPostProcessor接口,从而利用实现对外扩展函数postProcessBeanFactory来进行对 Spring 的扩展:

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (this.propertySources == null) {
            this.propertySources = new MutablePropertySources();
            if (this.environment != null) {
                this.propertySources.addLast(
                    new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
                        @Override
                        public String getProperty(String key) {
                            return this.source.getProperty(key);
                        }
                    }
                );
            }
            try {
                PropertySource<?> localPropertySource =
                        new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
                if (this.localOverride) {
                    this.propertySources.addFirst(localPropertySource);
                }
                else {
                    this.propertySources.addLast(localPropertySource);
                }
            }
            catch (IOException ex) {
                throw new BeanInitializationException("Could not load properties", ex);
            }
        }
        // 创建替换 ${...} 表达式的处理器
        processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
        this.appliedPropertySources = this.propertySources;
    }

上面的核心步骤是processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources)),这里会创建处理 ${...} 表达式的StringValueResolver:

protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
            final ConfigurablePropertyResolver propertyResolver) throws BeansException {
        // 设置占位符的前缀:"{"
        propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
        // 设置占位符的后缀:"}"
        propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
        // 设置默认值分隔符:":"
        propertyResolver.setValueSeparator(this.valueSeparator);
        // 生成处理 ${...} 表达式的处理器
        StringValueResolver valueResolver = new StringValueResolver() {
            @Override
            public String resolveStringValue(String strVal) {
                String resolved = (ignoreUnresolvablePlaceholders ?
                        propertyResolver.resolvePlaceholders(strVal) :
                        propertyResolver.resolveRequiredPlaceholders(strVal));
                if (trimValues) {
                    resolved = resolved.trim();
                }
                return (resolved.equals(nullValue) ? null : resolved);
            }
        };
        // 将处理器放入 Spring 容器
        doProcessProperties(beanFactoryToProcess, valueResolver);
    }

在上面的代码中,resolvePlaceholders表示如果变量无法解析则忽略,resolveRequiredPlaceholders表示如果变量无法解析则抛异常(默认情况)。最后将生成的StringValueResolver存入 Spring 容器中:

protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
            StringValueResolver valueResolver) {

        BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

        String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
        for (String curName : beanNames) {
            // Check that we're not parsing our own bean definition,
            // to avoid failing on unresolvable placeholders in properties file locations.
            if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
                BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
                try {
                    visitor.visitBeanDefinition(bd);
                }
                catch (Exception ex) {
                    throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
                }
            }
        }

        // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
        beanFactoryToProcess.resolveAliases(valueResolver);

        // 将 StringValueResolver 存入 BeanFactory 中
        beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
    }

最后将StringValueResolver实例注册到ConfigurableListableBeanFactory中,也就是在真正解析变量时使用的StringValueResolver实例。

经过resolveEmbeddedValue方法之后,我们就拿到了替换后的值,接下来就是与原 bean 进行整合了。其操作是在TypeConverter#convertIfNecessary方法中,这里分为两种情况:

  1. 如果目标类存在@Value修饰的属性。
    如:

@Configuration
public class RedisProperties {
    @Value("${redis.url}")
    private String url;
    geter/setter...
}

该情况直接通过反射调用目标 bean 的Field.set方法(注意,不是属性对应的 set 方法),直接给属性赋值。

  1. 如果目标类不存在@Value修饰的属性。
    如:

@Configuration
public class RedisProperties {
   
    @Value("${redis.url}")
    public void resolveUrl(String redisUrl){
      ...
      }
}

该情况依旧使用反射,调用Method.invoke方法,给方法参数进行赋值。

2.2 Enviroment 的初始化

这里面有一个关键点,就是在初始化MutablePropertySources的时候依赖的一个变量enviroment。Enviroment 是 Spring 所有配置文件转换为 KV 的基础,而后续的一系列操作都是在enviroment基础上做的进一步封装,那么我们就来探索一下enviroment的初始化时机。

enviroment的初始化过程并不是之前通用的在 PostProcessor 类型的扩展接口上做扩展,而是通过ConfigFileAoolicationListener监听机制完成的。我们看其onApplicationEvent监听方法:

public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent(
                    (ApplicationEnvironmentPreparedEvent) event);
        }
        if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent(event);
        }
    }

当加载完成配置文件之后,SpringBoot 就会发布ApplicationEnvironmentPreparedEvent事件,ConfigFileAoolicationListener监听到该事件之后,就会调用onApplicationEnvironmentPreparedEvent方法:

private void onApplicationEnvironmentPreparedEvent(
            ApplicationEnvironmentPreparedEvent event) {
        List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
        // 将 ConfigFileAoolicationListener 存入 postProcessors
        postProcessors.add(this);
        AnnotationAwareOrderComparator.sort(postProcessors);
        // 遍历执行 EnvironmentPostProcessor 的 postProcessEnvironment 方法
        for (EnvironmentPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessEnvironment(event.getEnvironment(),
                    event.getSpringApplication());
        }
    }

由于ConfigFileAoolicationListener实现了EnvironmentPostProcessor,于是这里首先将其纳入postProcessors,然后遍历postProcessors,执行其postProcessEnvironment方法,于是ConfigFileApplicationListener#postProcessEnvironment方法就会被执行:

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
            SpringApplication application) {
        // 将配置文件信息存入 environment
        addPropertySources(environment, application.getResourceLoader());
        configureIgnoreBeanInfo(environment);
        // 将 environment 与 Spring 应用上下文绑定
        bindToSpringApplication(environment, application);
    }

该方法的作用是将配置文件信息存入environment,并将environment与 Spring 应用上下文进行绑定。我们不妨深入addPropertySources方法,继续探讨配置文件读取流程,其核心流程是在ConfigFileApplicationListener.Loader#load()方法中:

    public void load() {
            this.propertiesLoader = new PropertySourcesLoader();
            this.activatedProfiles = false;
            this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
            this.processedProfiles = new LinkedList<Profile>();

            // 通过 profile 标记不同的环境,可以通过设置 spring.profiles.active 和 spring.profiles.default。
            // 如果设置了 active,default 便失去了作用。如果两个都没设置。那么带有 profiles 标识的 bean 不会被创建。
            Set<Profile> initialActiveProfiles = initializeActiveProfiles();
            this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
            if (this.profiles.isEmpty()) {
                for (String defaultProfileName : this.environment.getDefaultProfiles()) {
                    Profile defaultProfile = new Profile(defaultProfileName, true);
                    if (!this.profiles.contains(defaultProfile)) {
                        this.profiles.add(defaultProfile);
                    }
                }
            }

            // 支持不添加任何 profile 注解的 bean 的加载
            this.profiles.add(null);

            while (!this.profiles.isEmpty()) {
                Profile profile = this.profiles.poll();
                // SpringBoot 默认从 4 个位置查找 application.properties/yml 文件
                // classpath:/,classpath:/config/,file:./,file:./config/
                for (String location : getSearchLocations()) {
                    if (!location.endsWith("/")) {
                        // location is a filename already, so don't search for more
                        // filenames
                        load(location, null, profile);
                    }
                    else {
                        for (String name : getSearchNames()) {
                            load(location, name, profile);
                        }
                    }
                }
                this.processedProfiles.add(profile);
            }

            addConfigurationProperties(this.propertiesLoader.getPropertySources());
        }

这里涉及到我们以前经常用的 profile 机制,现在大部分公司都是使用配置中心(如 apollo)对配置文件统一管理的。SpringBoot 默认从 4 个位置查找 application.properties/yml 文件:classpath:/,classpath:/config/,file:./,file:./config/。

2.3 PropertySourcesPlaceholderConfigurer 的注册

上面提到StringValueResolver功能实现依赖 Spring 的切入点是PropertySourcesPlaceholderConfigurer,那么它又是何时创建的呢?

我们搜索该类的调用栈,发现其在PropertyPlaceholderAutoConfiguration中创建的:

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class PropertyPlaceholderAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

}

没错,他就是通过 SpringBoot 的自动装配特性创建的。

3. 小结

@Value的处理器StringValueResolver初始化时机是PropertySourcesPlaceholderConfigurer#postProcessBeanFactory中,而处理@Value属性解析的时机是在getBean中的依赖处理resolveDependency方法中。

4. 彩蛋

获取配置文件信息除了@Value以外,还可以使用@ConfigurationProperties,它是 SpringBoot 特有的,关于用法读者自己网上去搜,我这里只讲大概的原理。

SpringBoot 通过自动装配类ConfigurationPropertiesAutoConfiguration引入了ConfigurationPropertiesBindingPostProcessorRegistrar,它是ImportBeanDefinitionRegistrar的实现类,其registerBeanDefinitions方法会将ConfigurationPropertiesBindingPostProcessor的 bean 信息注入 Spring 容器。而ConfigurationPropertiesBindingPostProcessorBeanPostProcessor的实现类,于是会在 bean 实例化(调用 getBean )之前,调用AbstractApplicationContext#registerBeanPostProcessors方法,将其注册为beanPostProcessors。于是会在 bean 初始化之前,调用postProcessBeforeInitialization方法,该方法会解析@ConfigurationProperties注解,读取enviroment中的对应的配置,并且与当前对象进行绑定。

探讨下@Value@Bean的执行先后顺序!
在本文中,我们知道@Value属性解析的时机是在@Value所属的配置类在进行getBean时的依赖处理resolveDependency方法中,而@Bean注解的处理原理是,在refresh()时的invokeBeanFactoryPostProcessors(beanFactory)方法中,会根据@Bean修饰的方法作为factory-method(工厂方法),从而生成一个其返回值类型的BeanDefinition信息,并且存入 Spring 容器中。在该 Bean 实例化的时候,即在getBean时的createBeanInstance方法中,会进行实例化操作,就会调用@Bean修饰的方法。

于是@Value@Bean的执行先后顺序,取决于@Value所属的目标类@Bean修饰方法的返回类的加载先后顺序,而 Spring 默认情况下,加载这些没有依赖关系的 bean 是没有顺序的。要想干预他们的顺序,就必须加一些手段了,比如@DependsOn

但是如果@Value修饰的是@Bean的方法,比如:

    @Bean
    @Value("${access.environment}") 
    public EnvironmentTool environmentTool(String env) {
        EnvironmentTool environmentTool = new EnvironmentTool();
        environmentTool.setEnv(env);
        return environmentTool;
    }

此时@Value所属的目标类为@Bean修饰方法的返回类,由于getBeancreateBeanInstance方法中,在处理factory-method的时候,会调用instantiateUsingFactoryMethod方法,其底层会调用resolveDependency方法来处理其属性的填充逻辑,比如@Value的处理逻辑。最后会通过反射调用目标方法,即@Bean修饰的方法逻辑。所以,当@Value修饰的是@Bean的方法时,@Value的处理时机是早于@Bean所修饰的方法的。


转自:https://www.jianshu.com/p/933669270a9f

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值