介紹
在之前的文章中,寫了一篇使用Spring @Profile實現開發環境,測試環境,生產環境的切換,之前的文章是使用SpringBoot專案搭建,實現了不同環境資料來源的切換,在我們實際開發中,會分為dev,test,prod等環境,他們之間數獨立的,今天進來詳解介紹Spring @Profile的原理。
# Spring註解@Profile實現開發環境,測試環境,生產環境的切換
使用
帶有@Profile的註解的bean的不會被註冊進IOC容器,需要為其設定環境變數啟用,才能註冊進IOC容器,如下透過setActiveProfiles設定了dev值,那麼這三個值所對應的Bean會被註冊進IOC容器。當然,我們在實際使用中,不會這樣去做,使用SpringBoot的話,我們一般是使用yml,在yml中配置spring.profiles.active
,也可以透過配置jvm引數。
透過Environment設定profile
我們可以直接透過Environment來設定環境屬性,這是比較原生的方法。
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getEnvironment().setActiveProfiles("dev");
透過JVM引數設定
可以透過JVM引數來設定環境變數的值,在開發中,這種方式也是使用得比較普遍。
SpringBoot透過yml進行配置
在SpringBoot專案中,我們得配置項一般都是配置在yml檔案中,這樣就能和程式碼分開,並且也能進行動態配置。
從上面我們看出可以透過好幾種方式進行配置,但是他們最終其實都是將環境變數設定進Environment
中,這樣,spring在後續得流程裡面,就能從Environment中獲取環境變數,然後進行相應的邏輯處理。
原始碼解析
BeanDefinition註冊
首先,需要註冊bean的元資訊BeanDefinition,不過對於@Profile標註的方法,如果環境變數中有對應的變數值,那麼就能註冊,沒有的話則不會進行註冊,我們來看關鍵的程式碼,在ConfigurationClassBeanDefinitionReader中,有一個shouldSkip
判斷,它會篩選出符合的bean,不符合條件的bean則被加入skippedBeanMethods集合中,不會被註冊。
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中,會使用Condition介面,@Profile使用的是ProfileCondition
,然後呼叫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中尋找環境變數,然後解析@Profile註解設定的value值,如果Environment中啟用的配置中包含當前的配置,包含則能為true,不包含則為false,如上透過setActiveProfiles設定Environment中啟用的配置為dev,當前傳過來的配置為dev,那麼就能匹配上,就能裝配進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;
}
從原始碼可以看出,其最核心的思想就是是否註冊bean的元資訊BeanDefinition,因為只有註冊了BeanDefinition,後續才能為建立bean提供後設資料支援,判斷是否註冊bean元資訊,主要就是從Environment中取出profiles的值,然後和@Profile註解設定的值進行匹配,匹配得上就註冊,bean不上就不註冊。
總結
上面我們對@Profile的使用做了詳細的介紹,並對它的核心原始碼進行解剖,無非就是判斷是否要註冊BeanDefinition,如果我們需要做一些環境隔離的工作,使用@Profile還是比較不錯的。
今天的分享就到這裡,感謝你的觀看,下期見!