關于Spring中@Value注解使用和源碼分析
1、@Value 注解使用
先配置本地 application.properties
如下:
apple.name=abc
代碼如下:
@PropertySource("application.properties") public class Apple { @Value("${apple.name}") public String name; } @ComponentScan public class AtValueTest { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AtValueTest.class); Apple bean = context.getBean(Apple.class); System.out.println("bean.name = " + bean.name); } }
結果如下:
bean.name = abc
可以看到最終在 Apple 中可以獲取到配置文件中的值,那么 Spring 是怎樣解析獲取到該值的呢?下面開始分析下。
2、@Value 注解源碼分析
分析源碼之前,我么可以簡單思考下, Spring 會怎么樣去處理這個 @Value 注解的?然后再帶著我們的問題去看看 Spring 是不是和我們想的一樣呢?
簡單分析有三個步驟,如下:
- 首先 Spring 肯定要去解析收集該注解,找到被 @Value 注解修飾的所有屬性,注意此時屬性的值是一個占位符 ${apple.name},并不是真正的值
- 然后加載一個資源文件,可以是本地 application.properties、也可以是其他,比如 Environment 、xml
- 最后去判斷哪個類上的屬性有 @Value 修飾,就把占位符替換成資源文件中配置的值
在腦海中有了大致思路,再去追蹤源碼就事半功倍了。
2.1、Spring 解析并收集 @Value 修飾的屬性
Spring 解析收集 @Value 修飾的屬性和解析收集 @Autowired 注解一模一樣,懂這個,@Autowired 注解解析流程也就懂了。
Spring 提供很多 BeanFactoryPostProcessor、BeanPostProcessor 接口作為擴展,從而使 Spring 非常強大,因為我們屬性賦值相當于是在實例化之后的事,所以這里的 @Value 解析就是 BeanPostProcessor 接口的應用啦!
一個大家非常熟悉的類 AutowiredAnnotationBeanPostProcessor,Spring 的 DI(依賴注入) 就是這個 BeanPostProcessor 完成的,接下來看 Spring 源碼,如下:
可以看到 Spring 是在實例化之后開始去收集的,這個時序非常重要,一定要注意
然后進入 AutowiredAnnotationBeanPostProcessor 類核心部分,如下:
對過來的每個類進行篩選判斷是否有被 @Value、@Autowired 修飾的方法或者屬性,如果找到有,就會將這個類記錄下來,放到一個 injectionMetadataCache 緩存中,為后續(xù)的 DI 依賴注作準備,注意哦,解析并收集到的結果最終放到 Spring 的 injectionMetadataCache 緩存中
進入 buildAutowiringMetadata() 方法內部,如下:
獲取到這個類上所有的屬性,然后遍歷每個屬性,判斷是否有 @Value、@Autowired 修飾,如果有,直接封裝成 AutowiredFieldElement 對象,然后保存到一個名為 currElements List 容器中
最后在封裝到 InjectionMetadata 對象中,最終返回出去放到 injectionMetadataCache 緩存中保存,不用管他封裝到哪個對象,反正這里就是掃描并解析到了哪些屬性,或者方法后續(xù)需要做處理就可以了。
上面源碼中 findAutowiredAnnotation() 方法內部邏輯如下:
在 AutowiredAnnotationBeanPostProcessor 類創(chuàng)建的時候,Spring 就默認往 autowiredAnnotationTypes 容器中添加兩個元素,如下:
至此,解析收集 @Value 修飾的屬性已經完成,最終將收集到的結果放到了 injectionMetadataCache 緩存中保存,后續(xù)需要使用直接可以從這個緩存中獲取即可。
2.2、Spring 為 @Value 修飾屬性賦值
上面通過 postProcessMergedBeanDefinition() 方法收集好了 @Value 注解修飾的屬性,那么下面要做的就是去為這個屬性進行賦值。
進入屬性填充的方法,源碼如下:
然后進入到 AutowiredAnnotationBeanPostProcessor 類中,源碼如下:
注意此時的 injectionMetadataCache 緩存中早已經有值了,因為前面我們就已經收集完成了 @Value 修飾的屬性,所以這里直接從緩存中就可以獲取到。
然后進入 metadata.inject(bean, beanName, pvs)
代碼內部,如下:
最終通過 resolveFieldValue() 方法獲取到屬性值,然后通過反射 field.set() 方法給這個屬性賦值,如下:
至此,整個 @Value 的流程就算完成,下面就是對這個 resolveFieldValue() 方法進一步分析,看下是怎么獲取到屬性值的,是怎么樣將 $ 符號替換成解析成真正的值的
2.3、Spring $ 占位符替換成真正的值
繼續(xù)深入分析 resolveFieldValue() 方法,核心源碼如下:
下面這段邏輯是去解析 ${apple.name} 占位符的,這里面為什么需要遞歸 parseStringValue(),因為怕你出現(xiàn)這種形式的占位符 ${ ${apple.name} },最終獲取到 key = apple.name,然后拿著這個 key 就去資源文件(xml、application.properties、Environment 等)中查找是否配置了這個 key 的值
注意這里是函數(shù)式寫法,傳入一個方法體 this::getPropertyAsRawString,后面會回調到這里。
當調用 resolvePlaceholder() 方法時,回調到 getPropertyAsRawString() 方法,源碼如下:
可以看到最終會調用到 getProperty() 方法獲取到對應 key = apple.name 的值
從 propertySources 資源中獲取 key = apple.name 的值,只要在這里獲取到一個值就直接 return 出去即可
propertySources 這里會有三個,如下所示:
PropertiesPropertySource
:封裝操作系統(tǒng)屬性鍵值對SystemEnvironmentPropertySource
:封裝 JVM Environment 里面的鍵值對ResourcePropertySource
:封裝 application.properties、xml 中鍵值對
然后 debug 發(fā)現(xiàn)最終是從 ResourcePropertySource 資源對象中獲取到 apple.name 對應的值 abc,最終將 ${apple.name} 替換成真正的值 abc,最終通過反射將該值 abc 賦值到 Apple 類中的 name 屬性上。
那么這里肯定有很多人會有這樣的疑問?這三個對象是從哪里來的呢?如果要想知道這個,需要下面一些知道做鋪墊,那么繼續(xù)往下看 !
2.4、理解 PropertySource 和 MutablePropertySource
在 Spring 中需要加載一些額外的配置文件,比如操作系統(tǒng)相關的配置,JVM 環(huán)境變量相關的配置,自定義配置文件等等。那么這些文件加載到代碼中可定要有一個類來封裝它,這個類就是 PropertySource,先來看看 PropertySource 的源碼如下:
public abstract class PropertySource<T> { protected final Log logger = LogFactory.getLog(getClass()); // 給這個配置文件起個名字唄 protected final String name; // 配置文件中所有的 key-value 鍵值對 // T 只要是 key-value 鍵值對即可,比如: Map、Properties 都可以 protected final T source; public String getName() { return this.name; } public T getSource() { return this.source; } // 根據(jù) name 獲取到對應的配置文件 @Nullable public abstract Object getProperty(String name); }
看完這個 PropertySource 類的結構,我們看看上面三個類中封裝的屬性到底是啥?如下所示:
PropertiesPropertySource
:封裝操作系統(tǒng)屬性鍵值對(os.name、file.encoding、user.name 等等)
SystemEnvironmentPropertySource
:封裝 JVM Environment 里面的鍵值對(PATH、JAVA_HOME)
ResourcePropertySource
:封裝 application.properties、xml 中鍵值對
理解了 PropertySource 這個類之后,在來理解 MutablePropertySource 就非常容易。
先來看看 MutablePropertySource 的源碼,如下:
public class MutablePropertySources { private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>(); }
從源碼中可以看到,就是做了一個收集,將所有的 PropertySource 收集到一個 propertySourceList 容器中進行管理,至于為什么這樣做呢?
個人理解是為了更方便的查找屬性,將所有的資源文件匯集到一起,然后想要找一個 key = apple.name 就可以直接遍歷一下所有的資源文件,看下這個 key 在哪個資源文件中能夠找到,找到立即返回。
但是此時就會存在一個問題,那就是文件的加載順序,可以發(fā)現(xiàn)最先加載 PropertiesPropertySource、其次 SystemEnvironmentPropertySource,最后才是自定義的配置文件 ResourcePropertySource!比如,我們在 application.properties 中配置 user.home=abc 如下所示:
user.home=abc
@Component @PropertySource("application.properties") public class Apple { @Value("${user.home}") public String name; }
輸出結果:
name = /Users/gongwm
并不是 abc,因為 PropertiesPropertySource 優(yōu)先于 ResourcePropertySource 被加載。
下面這個源碼是獲取資源文件,可以發(fā)現(xiàn)只要獲取到就會立即 return,所以最終決定權交給了往 propertySources 容器添加的順序決定,那么我們來看看上面三個文件分別是在什么時候方進去的?
2.5、Spring 資源文件裝載源碼分析?
直接進入源碼分析,如下:
然后再 StandardEnvironment 的構造方法中,隱式調用父類 AbstractEnvironment 的構造方法,源碼如下:
可以發(fā)現(xiàn)在這里直接 new 創(chuàng)建了 MutablePropertySources 對象
最終可以發(fā)現(xiàn)這兩個 systemProperties、systemEnvironment 資源文件都是在這里被加載的,添加到了 MutablePropertySources 對象中
對于自定義的配置文件在 ConfigurationClassPostProcessor 類中被加載,源碼如下:
最終就是在這里被加載進去的,注意這個添加方法是 addLast() 也就是往后面追加,這個方法就體現(xiàn)了這些資源文件的加載順序,那么有 addLast() ,必然有 addFirst() 等等 API,此時 propertySourceList 容器中就已經保存了三個資源文件,并且順序是這樣的PropertiesPropertySource(優(yōu)先級最高) -> SystemEnvironmentPropertySource -> ResourcePropertySource (優(yōu)先級最低)
最后有一點要注意,不要把 MutablePropertySources 和 MutablePropertyValues 搞混了,兩完全不是一一碼事!具體想看 MutablePropertyValues 是啥,可以轉到另一篇文章!
2.6、在 Environment 中添加自定義屬性
借助 BeanDefinitionRegistryPostProcessor 類來實現(xiàn)這個功能,如下所示:
@Component public class AppendAttrToEnvironment implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware { private ResourceLoader resourceLoader; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // 從容器中獲取到 Environment 對象 StandardEnvironment bean = (StandardEnvironment)beanFactory.getBean(Environment.class); // 然后再獲取到 MutablePropertySources 資源文件管家(它會收集打包所有的資源文件) MutablePropertySources propertySources = bean.getPropertySources(); // 創(chuàng)建第一個資源文件,其中有個屬性 key666 值為 hangman Properties properties = new Properties(); properties.put("key666", "hangman"); PropertiesPropertySource propertiesSource = new PropertiesPropertySource("myPropertySource",properties); // 添加到 MutablePropertySources 資源文件管家中,注意使用 addLast() 添加的,也就是往后追加 propertySources.addLast(propertiesSource); // 創(chuàng)建第二資源文件,看下面的 abc.properties 配置文件 Resource resource = resourceLoader.getResource("abc.properties"); ResourcePropertySource localResource = new ResourcePropertySource("myResourceSource",resource); // 添加到 MutablePropertySources 資源文件管家中,注意是添加到最前面了,這樣就會被最先加載 propertySources.addFirst(localResource); } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {} @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } }
創(chuàng)建一個 abc.properties
配置文件如下所示:
key666 = 6666key777 = ggg
然后在一個 Apple 類中去獲取對應的值,如下所示:
@Component @PropertySource("application.properties") public class Apple implements EnvironmentAware { @Value("${apple.name}") public String name; @Override public void setEnvironment(Environment environment) { String property = environment.getProperty("apple.name"); String key666 = environment.getProperty("key666"); String key777 = environment.getProperty("key777"); System.out.println("key666 = " + key666); System.out.println("key777 = " + key777); System.out.println("name = " + name); System.out.println("property = " + property); } }
輸出結果如下:
key666 = 6666
key777 = ggg
name = abc
property = abc
bean.name = abc
這里會發(fā)現(xiàn)一個問題,key = key666 的這個鍵值對,在 propertiesSource 資源和 localResource 資源文件同時出現(xiàn),最終因為 localResource 資源是通過 addFirst() 方法添加到 MutablePropertySources 管家容器最前面,優(yōu)先生效。
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Logger.getLogger()與LogFactory.getLog()的區(qū)別詳解
LogFactory來自common-logging包。如果用LogFactory.getLog,你可以用任何實現(xiàn)了通用日志接口的日志記錄器替換log4j,而程序不受影響2013-09-09Mybatis-plus與Mybatis依賴沖突問題解決方法
,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧這篇文章主要介紹了Mybatis-plus與Mybatis依賴沖突問題解決方法2021-04-04Java concurrency集合之CopyOnWriteArraySet_動力節(jié)點Java學院整理
CopyOnWriteArraySet基于CopyOnWriteArrayList實現(xiàn),其唯一的不同是在add時調用的是CopyOnWriteArrayList的addIfAbsent(若沒有則增加)方法2017-06-06java?數(shù)組實現(xiàn)學生成績統(tǒng)計教程
這篇文章主要介紹了java?數(shù)組實現(xiàn)學生成績統(tǒng)計教程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12