介绍
@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
后置处理器进行处理。