Spring @Value的注解使用和原理解析

240

介绍

@Value注解在Spring开发中是一个使用很频繁的注解,在项目开发中,我们通常需要读取配置文件中的一些信息,对于SpringBoot项目,我们一般从yml文件中读取,如果我们自定义了配置文件,那么就可以配合@PropertySource注解来获取配置文件的配置项,当然,@Value不单单能读取配置文件,还能读取系统属性,还可以读取其他bean的属性,本章就来详细介绍@Value注解的使用和对源码进行分析。

使用

如下我们对value的使用进行详细介绍,value可以注入配置文件的属性,注入其它bean的属性,注册spring中自己实现的一些属性,比如操作系统信息。

属性类

MyProperties是一个bean,里面定义了一些属性,一般在项目中,如果需要全局使用某个配置信息,我们通常会定义一个属性类,然后在需要使用的地方直接注入,比如系统中我们需要存储大量的文件,文件是存储在文件服务器上面,数据库只存储文件所在文件系统的目录路径,而不会存储具体的ip地址,如果我们存储了能直接访问文件的链接,后续如果进行文件迁移,那么这些链接就不好处理,所以应该只存储文件在文件服务器的目录路径,那么返回给前端显示的时候,再获取文件服务器地址进行拼接就可以。

/**
 * 功能说明: 属性配置类
 * <p>
 * Original @Author: steakliu-刘牌, 2023-04-27  10:08
 * <p>
 * Copyright (C)2020-2022  steakliu All rights reserved.
 */
@Data
@Component
public class MyProperties {
    /**
     * 注入其他bean的属性
     */
    @Value("#{valueBean.username}")
    private String username;
    /**
     * 注入配置文件属性
     */
     @Value("${minio.url}")
    private String minioUrl;
    /**
     * 注入操作系统属性
     */
    @Value("#{systemProperties['os.name']}")
    private String os;
}

配置类

配置类主要就是使用@PropertySource注解来获取配置配置文件的属性。

@Configuration
@PropertySource("classpath:minio.properties")
public class ValueConfiguration {

}

配置文件minio.properties

配置文件里面就放了一个minio的地址

minio.url=http://www.gss.cn/

通过上面的配置,我们就可以在需要使用minio地址的地方注入MyProperties这bean就可以,可能有些人会觉得麻烦,还需要注入bean,直接写在一个常量里面不就行,其实不然,这样做更加的规范,做到了配置和代码的分离,不同的环境的地址不同,或者发生文件迁移,就可以直接修改配置文件,还有配置文件可以写入注册中心,可以更具一定的策略进行修改后刷新,@Value注解只是获取配置文件属性的一种方式,在SpringBoot中,@ConfigurationProperties使用起来也很方便。

原理解析

下面对@Value的原理进行解析,因为我们使用@Value大多时候是放在字段上面,并且要使用在一个Bean中,那么我们知道bean在实例化的时候需要进行属性填充,就会对这些属性进行赋值,所以下面就从实例化bean开始对@Value进行解析。

解析属性

我们从AbstractAutowireCapableBeanFactory类这里开始,在类中进入doCreateBean()方法,然后进入applyMergedBeanDefinitionPostProcessors,最终会进入AutowiredAnnotationBeanPostProcessor后置处理器中,@Autowired,@Value,@Inject都是它进行处理,下面我们看最主要的部分buildAutowiringMetadata

如下代码,Spring使用反射获取字段,如果是字段被static修饰,那么在此处是会被排除,使用的是Modifier.isStatic(int mod)方法,通过反射拿到字段后,组装后加入一个名字为injectionMetadataCache的Map中,后面属性填充会直接从这个缓存中获取。

private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
        List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
        Class<?> targetClass = clazz;
        do {
            final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
            ReflectionUtils.doWithLocalFields(targetClass, field -> {
                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;
                    }
                    boolean required = determineRequiredStatus(ann);
                    currElements.add(new AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement(field, required));
                }
            });
        }
        while (targetClass != null && targetClass != Object.class);

        return InjectionMetadata.forElements(elements, clazz);
    }

属性填充

属性填充阶段进入的是对bean的属性进行赋值,这是Spring生命周期中很重要的一个阶段,方法是populateBean,也在AbstractAutowireCapableBeanFactory类中,接着会调用AutowiredAnnotationBeanPostProcessor中的postProcessProperties方法,然后往下继续执行,核心代码如下,如下就是给每个属性赋值,往下执行还有很多逻辑处理,如解析@Value注解的表达式,然后根据表达式去获取对应的值等,就不深入去解析。

    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);
        }
    }

总结

上面对@Value的使用和原理进行了介绍,其实@Value,@Autowired,@Resource,@Inject这几个的作用都是进行属性装配,只不过他们的方式各有不同,@Value,@Autowired,@Inject是使用AutowiredAnnotationBeanPostProcessor后置处理器进行处理,@Resource则使用CommonAnnotationBeanPostProcessor后置处理器进行处理。

今天的分享就到这里,感谢你的观看,我们下期见!