springboot @Value实现获取计算机中绝对路径文件的内容

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我们通常配置在配置文件中以区分不同环境。具体的依赖关系如下图:

springboot @Value实现获取计算机中绝对路径文件的内容_第1张图片

源码解读分析

首先看一下Spring Bean的的整个生命周期:

springboot @Value实现获取计算机中绝对路径文件的内容_第2张图片

在实例化对象完成后,设置属性值(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的初始化等工作。

springboot @Value实现获取计算机中绝对路径文件的内容_第3张图片

2、然后进入到AbstractApplicationContext的refresh()方法

中的finishBeanFactoryInitialization(beanFactory)方法,这个方法就是ApplicationContext完成Spring Bean实例化和初始化的关键方法。

springboot @Value实现获取计算机中绝对路径文件的内容_第4张图片

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)方法中。这个方法代码较多,我们直接查看我们要关注的核心代码段如下:

springboot @Value实现获取计算机中绝对路径文件的内容_第5张图片

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方法。

springboot @Value实现获取计算机中绝对路径文件的内容_第6张图片

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注解的字段。

springboot @Value实现获取计算机中绝对路径文件的内容_第7张图片

5.2、我们继续进入到

findAutowiringMetadata(String beanName, Class clazz, @Nullable PropertyValues pvs)方法,通过buildAutowiringMetadata(final Class clazz)首先获取了当前bean所属类的属性注解元数据,然后存储到了injectionMetadataCache缓存中,完成了applyMergedBeanDefinitionPostProcessors方法。

springboot @Value实现获取计算机中绝对路径文件的内容_第8张图片

5.3、我们进入到

buildAutowiringMetadata(final Class clazz)查看构建bean注入注解元数据的过程

springboot @Value实现获取计算机中绝对路径文件的内容_第9张图片

其中用到了获取当前成员属性AnnotationAttributes的方法findAutowiredAnnotation(AccessibleObject ao)如下:

springboot @Value实现获取计算机中绝对路径文件的内容_第10张图片

其中autowiredAnnotationTypes表示后置处理器AutowiredAnnotationBeanPostProcessor处理的注解列表,初始化如下:

springboot @Value实现获取计算机中绝对路径文件的内容_第11张图片

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的成员属性的初始化和依赖的注入过程。

springboot @Value实现获取计算机中绝对路径文件的内容_第12张图片

6.2、因为我们要处理的是@Value和@Autowired注解的bean依赖

AutowiredAnnotationBeanPostProcessor是InstantiationAwareBeanPostProcessor接口的实现类,所以上面代码端循环过程中会进入到之前的AutowiredAnnotationBeanPostProcessor的postProcessProperties(PropertyValues pvs, Object bean, String beanName)。

springboot @Value实现获取计算机中绝对路径文件的内容_第13张图片

6.3、上面findAutowiringMetadata方法就是第5.2步的方法

也就是从缓存Map中取出bean对应的注解元数据,而后调用InjectionMetadata的inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs)方法执行依赖注入过程。

springboot @Value实现获取计算机中绝对路径文件的内容_第14张图片

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方法。

springboot @Value实现获取计算机中绝对路径文件的内容_第15张图片

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方法。

springboot @Value实现获取计算机中绝对路径文件的内容_第16张图片

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解析器完成,其内部使用了默认的占位符如下:

springboot @Value实现获取计算机中绝对路径文件的内容_第17张图片

通过其doResolvePlaceholders(String text, PropertyPlaceholderHelper helper)方法完成解析,内部调用了PropertyPlaceholderHelper的parseStringValue(String value, PlaceholderResolver placeholderResolver, Set visitedPlaceholders)方法来具体实现注解的值的解析和占位替换。解析首先是从配置文件对应的PropertySource中完成配置和值读取,最终通过MapPropertySource实现。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

你可能感兴趣的