springboot @Value获取绝对路径文件的内容
默认情况下使用
@Value("aaa.txt")
private Resource txtResource;
这样获取到的是项目classpath 下的 aaa.txt
如果想获取非项目路径下的文件内容怎么办呢,看了下@Value的好像也没有说,
其实
@Value("https://www.baidu.com")
private Resource urlResource;
这样是可以获取到 百度首页的内容的.它这里使用的是https协议.
同样的我们可以使用file协议获取文本的内容
即:
@Value("file:///E://aaa.txt")
private Resource txtResource;
使用@Value 有一个好处就是,你不用关心文本内容的变化,你每次调用的时候,springboot 会自动帮你重新加载.
Spring注解@Value解读
主要通过源码解读来分析@Value实现属性注入Spring Bean的过程,并对static类型字段无法通过@Value注入为Spring Bean依赖的原因做一个探究。
依赖注入概述
基于Spring MVC或者Spring Boot开发后端项目的时候总是绕不开Spring IOC容器,Spring IOC容器管理Spring Bean,我们可以通过XML或者注解的方式来定义一个Bean,如通过注解@Service,@Controller,@Component,@Repository或者@Bean加Java配置方式。实际应用中我们定义的一个Bean很多时候存在互相之间的依赖,比如Service层通过@Service定义的Bean往往要依赖数据库DAO层通过@Repository定义的Bean,这时候我们往往通过@Autowired或@Resource来自动装配,另外还有一些Bean内部的属性(Field)需要通过配置文件中定义的值来设置,而后创建的Bean才是符合我们预期的,我们一般通过@Value和@ConfigurationProperties来实现属性的注入。
实际应用案例
以文档管理服务为例,我们有个资源上传的接口,其依赖一个FeignClient客户端Bean,一个业务逻辑处理的Service Bean,还依赖一个资源上传的OSS的桶bucket成员变量,bucket和OSS我们通常配置在配置文件中以区分不同环境。具体的依赖关系如下图:
源码解读分析
首先看一下Spring Bean的的整个生命周期:
在实例化对象完成后,设置属性值(polulateBean)之前,会搜集类上的注解元数据信息,然后在polulalteBean中拦截,执行BeanPostProcessor中的方法,反射注入依赖的值。@Resource是jdk提供的注解,其使用的后置处理器是CommonAnnotationBeanPostProcessor,而@Autowired和@Value注解使用的后置处理器是AutowiredAnnotationBeanPostProcessor,具体的我们下面会通过代码来解读。
先来看一下BeanFactory和ApplicationContext在装载bean时候的区别:
- BeanFactory在启动时不会去实例化Bean,当从容器中拿Bean的时候才会去实例化;
- ApplicationContext在启动时就把所有的Bean全部实例化了。可以为Bean配置lazy-init=true来让Bean延迟实例化。
我们下面以Spring Boot和ApplicationContext来走读一下源码:
1、首先进入SpringApplication.run(XXXApplication.class, args)
然后一直进入到SpringApplication的refreshContext()方法,refreshContext()是IOC容器初始化的核心方法,完成容器上下文的刷新,主要包括各种处理器、监听器以及Bean的初始化等工作。
2、然后进入到AbstractApplicationContext的refresh()方法
中的finishBeanFactoryInitialization(beanFactory)方法,这个方法就是ApplicationContext完成Spring Bean实例化和初始化的关键方法。
3、而后进入到DefaultListableBeanFactory的preInstantiateSingletons()方法
此方法就是获取到所有的beanName,然后循环的去实例化+初始化bean,而实例化+初始化的方式就是调用其getBean(String name)方法,因此我们直接调到getBean(name) ,此方法的最终实现是在AbstractBeanFactory中,我们进入AbstractBeanFactory的getBean(beanName)查看,然后根据调用栈直接进入到AbstractBeanFactory的doGetBean(final String name, @Nullable final Class requiredType, @Nullable final Object[] args, boolean typeCheckOnly)方法中。这个方法代码较多,我们直接查看我们要关注的核心代码段如下:
4、然后我们进入到AbstractAutowireCapableBeanFactory
createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法中,其中AbstractAutowireCapableBeanFactory是AbstractBeanFactory的一个子类,AbstractBeanFactory的createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)由AbstractAutowireCapableBeanFactory实现。createBean方法的核心是调用了AbstractAutowireCapableBeanFactory的doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args),我们直接查看doCreateBean方法。
5、我们略过创建实例的过程
直接进入applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName)方法。
5.1、applyMergedBeanDefinitionPostProcessors方法
主要是循环所有后置处理器对beanDefinition做处理,我们之前说过@Value注解是通过AutowiredAnnotationBeanPostProcessor来处理的,我们直接进入AutowiredAnnotationBeanPostProcessor的postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName)方法如下:
其中的InjectionMetadata如下,它主要封装了bean的全限定类名和这个类中所有带有注解的成员属性(字段或者其他bean)描述(PropertyDescriptor,可以简单理解为带注解的成员属性和注解本身两者的组合),当然也就包括由@Value注解的字段。
5.2、我们继续进入到
findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs)方法,通过buildAutowiringMetadata(final Class<?> clazz)首先获取了当前bean所属类的属性注解元数据,然后存储到了injectionMetadataCache缓存中,完成了applyMergedBeanDefinitionPostProcessors方法。
5.3、我们进入到
buildAutowiringMetadata(final Class<?> clazz)查看构建bean注入注解元数据的过程
其中用到了获取当前成员属性AnnotationAttributes的方法findAutowiredAnnotation(AccessibleObject ao)如下:
其中autowiredAnnotationTypes表示后置处理器AutowiredAnnotationBeanPostProcessor处理的注解列表,初始化如下:
6、第5步完成了AbstractAutowireCapableBeanFactory
doCreateBean方法中的applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName),即完成了bean的成员属性注解元数据的本地缓存。可以理解applyMergedBeanDefinitionPostProcessors为一个拦截器,即在对bean进行成员属性和依赖注入前先预处理bean的依赖和成员属性数据的解析和缓存,而后再进行populateBean。
6.1、进入到AbstractAutowireCapableBeanFactory
populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw)方法,我们直接看核心代码如下,其中postProcessProperties方法就是完成了bean的成员属性的初始化和依赖的注入过程。
6.2、因为我们要处理的是@Value和@Autowired注解的bean依赖
AutowiredAnnotationBeanPostProcessor是InstantiationAwareBeanPostProcessor接口的实现类,所以上面代码端循环过程中会进入到之前的AutowiredAnnotationBeanPostProcessor的postProcessProperties(PropertyValues pvs, Object bean, String beanName)。
6.3、上面findAutowiringMetadata方法就是第5.2步的方法
也就是从缓存Map中取出bean对应的注解元数据,而后调用InjectionMetadata的inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs)方法执行依赖注入过程。
6.4、在上面对注解元数据列表循环处理(bean依赖了多个其他的bean或者多个成员属性通过配置注入)进行依赖注入
调用element.inject(target, beanName, pvs)方法。而AutowiredAnnotationBeanPostProcessor类的内部类AutowiredFieldElement继承了InjectionMetadata.InjectedElement类,并重写了其inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs)方法,AutowiredAnnotationBeanPostProcessor类的内部类AutowiredMethodElement也继承了InjectionMetadata.InjectedElement类,并重写了其inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs)方法,因此6.3中的inject调用将会在此子类中执行,AutowiredFieldElement处理成员属性上注解,AutowiredMethodElement处理方法上注解。因此@Value注解的处理将会进入到AutowiredFieldElement的inject方法。
6.5、首先通过beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter)方法解析依赖
然后注册当前bean依赖的其他bean,最后通过反射将依赖注入到当前bean中完成bean的依赖注入过程。DefaultListableBeanFactory继承了AbstractAutowireCapableBeanFactory类,我们先查看DefaultListableBeanFactory的resolveDependency方法,其中主要是调用了DefaultListableBeanFactory的doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter)方法,我们直接看doResolveDependency方法。
6.6、以上解析@Value注解
getSuggestedValue返回的值就是${xxx.documentcenter.bucketName:ssdocumentcenterstatic},因为是字符串,所以进入到下面的逻辑AbstractBeanFactory的resolveEmbeddedValue(@Nullable String value)方法中。resolveEmbeddedValue将完成@Value注解值的解析过程,最终得到配置文件中配置的值ssdocumentcenterstatic。下面的TypeConverter的convertIfNecessary主要完成数据类型的转换,比如配置文件中配置的Boolean类型,将String转为Boolean等。最后返回value值并注入到bean中,此成员属性注入到bean的过程就完成了。我们简单看一下resolveEmbeddedValue解析过程,主要通过AbstractPropertyResolver解析器完成,其内部使用了默认的占位符如下:
通过其doResolvePlaceholders(String text, PropertyPlaceholderHelper helper)方法完成解析,内部调用了PropertyPlaceholderHelper的parseStringValue(String value, PlaceholderResolver placeholderResolver, Set visitedPlaceholders)方法来具体实现注解的值的解析和占位替换。解析首先是从配置文件对应的PropertySource中完成配置和值读取,最终通过MapPropertySource实现。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。