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實現。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:https://www.cnblogs.com/mysgk/p/9427282.html