詳解Spring?@Profile注解的使用和源碼解析
介紹
在之前的文章中,寫了一篇使用Spring @Profile實(shí)現(xiàn)開發(fā)環(huán)境,測(cè)試環(huán)境,生產(chǎn)環(huán)境的切換,之前的文章是使用SpringBoot項(xiàng)目搭建,實(shí)現(xiàn)了不同環(huán)境數(shù)據(jù)源的切換,在我們實(shí)際開發(fā)中,會(huì)分為dev,test,prod等環(huán)境,他們之間數(shù)獨(dú)立的,今天進(jìn)來(lái)詳解介紹Spring @Profile的原理。
Spring注解@Profile實(shí)現(xiàn)開發(fā)環(huán)境,測(cè)試環(huán)境,生產(chǎn)環(huán)境的切換
使用
帶有@Profile的注解的bean的不會(huì)被注冊(cè)進(jìn)IOC容器,需要為其設(shè)置環(huán)境變量激活,才能注冊(cè)進(jìn)IOC容器,如下通過(guò)setActiveProfiles設(shè)置了dev值,那么這三個(gè)值所對(duì)應(yīng)的Bean會(huì)被注冊(cè)進(jìn)IOC容器。當(dāng)然,我們?cè)趯?shí)際使用中,不會(huì)這樣去做,使用SpringBoot的話,我們一般是使用yml,在yml中配置spring.profiles.active,也可以通過(guò)配置jvm參數(shù)。
通過(guò)Environment設(shè)置profile
我們可以直接通過(guò)Environment來(lái)設(shè)置環(huán)境屬性,這是比較原生的方法。
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getEnvironment().setActiveProfiles("dev");
通過(guò)JVM參數(shù)設(shè)置
可以通過(guò)JVM參數(shù)來(lái)設(shè)置環(huán)境變量的值,在開發(fā)中,這種方式也是使用得比較普遍。

SpringBoot通過(guò)yml進(jìn)行配置
在SpringBoot項(xiàng)目中,我們得配置項(xiàng)一般都是配置在yml文件中,這樣就能和代碼分開,并且也能進(jìn)行動(dòng)態(tài)配置。

從上面我們看出可以通過(guò)好幾種方式進(jìn)行配置,但是他們最終其實(shí)都是將環(huán)境變量設(shè)置進(jìn)Environment中,這樣,spring在后續(xù)得流程里面,就能從Environment中獲取環(huán)境變量,然后進(jìn)行相應(yīng)的邏輯處理。
源碼解析
BeanDefinition注冊(cè)
首先,需要注冊(cè)bean的元信息BeanDefinition,不過(guò)對(duì)于@Profile標(biāo)注的方法,如果環(huán)境變量中有對(duì)應(yīng)的變量值,那么就能注冊(cè),沒(méi)有的話則不會(huì)進(jìn)行注冊(cè),我們來(lái)看關(guān)鍵的代碼,在ConfigurationClassBeanDefinitionReader中,有一個(gè)shouldSkip判斷,它會(huì)篩選出符合的bean,不符合條件的bean則被加入skippedBeanMethods集合中,不會(huì)被注冊(cè)。
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
// Do we need to mark the bean as skipped by its condition?
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeanMethods.add(methodName);
return;
}
if (configClass.skippedBeanMethods.contains(methodName)) {
return;
}
}
shouldSkip源碼
在shouldSkip中,會(huì)使用Condition接口,@Profile使用的是ProfileCondition,然后調(diào)用matches方法。
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationCondition.ConfigurationPhase phase) {
for (Condition condition : conditions) {
ConfigurationCondition.ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition configurationCondition) {
requiredPhase = configurationCondition.getConfigurationPhase();
}
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
ProfileCondition匹配
在ProfileCondition的matches方法中,主要就是去Environment中尋找環(huán)境變量,然后解析@Profile注解設(shè)置的value值,如果Environment中激活的配置中包含當(dāng)前的配置,包含則能為true,不包含則為false,如上通過(guò)setActiveProfiles設(shè)置Environment中激活的配置為dev,當(dāng)前傳過(guò)來(lái)的配置為dev,那么就能匹配上,就能裝配進(jìn)IOC容器。
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
從源碼可以看出,其最核心的思想就是是否注冊(cè)bean的元信息BeanDefinition,因?yàn)橹挥凶?cè)了BeanDefinition,后續(xù)才能為創(chuàng)建bean提供元數(shù)據(jù)支持,判斷是否注冊(cè)bean元信息,主要就是從Environment中取出profiles的值,然后和@Profile注解設(shè)置的值進(jìn)行匹配,匹配得上就注冊(cè),bean不上就不注冊(cè)。
總結(jié)
上面我們對(duì)@Profile的使用做了詳細(xì)的介紹,并對(duì)它的核心源碼進(jìn)行解剖,無(wú)非就是判斷是否要注冊(cè)BeanDefinition,如果我們需要做一些環(huán)境隔離的工作,使用@Profile還是比較不錯(cuò)的。
到此這篇關(guān)于詳解Spring @Profile注解的使用和源碼解析的文章就介紹到這了,更多相關(guān)Spring @Profile注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java8 Stream Collectors收集器使用方法解析
這篇文章主要介紹了Java8 Stream Collectors收集器使用方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
java數(shù)據(jù)庫(kù)開發(fā)之JDBC的完整封裝兼容多種數(shù)據(jù)庫(kù)
這篇文章主要介紹了java數(shù)據(jù)庫(kù)開發(fā)之JDBC的完整封裝兼容多種數(shù)據(jù)庫(kù),需要的朋友可以參考下2020-02-02
java中的編碼轉(zhuǎn)換過(guò)程(以u(píng)tf8和gbk為例)
這篇文章主要介紹了java中的編碼轉(zhuǎn)換過(guò)程(以u(píng)tf8和gbk為例),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
DTO 實(shí)現(xiàn) service 和 controller 之間值傳遞的操作
這篇文章主要介紹了DTO 實(shí)現(xiàn) service 和 controller 之間值傳遞的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02
Java 實(shí)現(xiàn)LZ78壓縮算法的示例代碼
這篇文章主要介紹了Java 實(shí)現(xiàn)LZ78壓縮算法的示例代碼,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05
springmvc Controller方法沒(méi)有加@ResponseBody導(dǎo)致api訪問(wèn)404問(wèn)題
這篇文章主要介紹了springmvc Controller方法沒(méi)有加@ResponseBody導(dǎo)致api訪問(wèn)404問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
java后臺(tái)啟動(dòng)jar包的一些命令匯總
這篇文章主要介紹了java后臺(tái)啟動(dòng)jar包的一些命令匯總,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-03-03

