Spring @Value的注解使用和原理解析
作者:刘牌
介绍
@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
后置处理器进行处理。
以上就是Spring @Value的注解使用和原理解析的详细内容,更多关于Spring @Value注解的资料请关注脚本之家其它相关文章!