Spring條件注解沒生效該如何解決
從 Spring4.0 開始,Spring 提供了一個更加細粒度的條件注解: ConfigurationCondition。從名字上就可以看出來這個是搭配 @Configuration 注解一起使用的,ConfigurationCondition 提供了一種更加細粒度的條件匹配,可以在配置或者 Bean 注冊的時候去評估條件注解是否滿足。
也就是說,當一個類上存在條件注解的時候,我們可以有兩個評估條件注解是否滿足的時機:
- 在配置的時候去評估。
- 在 Bean 注冊的時候評估。
在配置的時候評估,可能會導(dǎo)致當前類都不會被加載,在 Bean 注冊的時候再去評估,意味著當前類就會被加載。
1. ConfigurationCondition
我們先來看下這個類的定義:
public interface ConfigurationCondition extends Condition {
ConfigurationPhase getConfigurationPhase();
enum ConfigurationPhase {
PARSE_CONFIGURATION,
REGISTER_BEAN
}
}大家看到,這里其實就是定義了兩個枚舉值,然后提供了一個方法返回枚舉值。
- PARSE_CONFIGURATION:這個表示 Condition 條件應(yīng)該在解析 @Configuration 類時進行評估,如果評估不通過,則不會將 @Configuration 添加到容器中。
- REGISTER_BEAN:這個表示添加常規(guī) Bean 的時候去評估 Condition 條件(常規(guī) Bean 就是指非配置類,例如添加搭配 @Bean 注解使用的條件注解),這個條件不會阻止注冊 @Configuration 類到容器中。
其實道理很好懂,就是加載配置類的時候就根據(jù)條件注解判斷要不要加載配置類,還是等到注冊 Bean 的時候再去看條件注解是否滿足條件。
2. 案例分析
松哥通過一個簡單案例來和小伙伴們演示一下。
假設(shè)我現(xiàn)在有如下條件:
public class MyCondition implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.PARSE_CONFIGURATION;
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getBeanFactory().containsBean("a");
}
}這個條件我沒有直接實現(xiàn) Condition 接口,而是實現(xiàn)類 ConfigurationCondition 接口,在這個接口中,getConfigurationPhase 方法返回了 PARSE_CONFIGURATION,表示在加載配置類的時候就去評估條件是否滿足,matches 方法則是去判斷容器中是否存在一個名為 a 的 Bean。
現(xiàn)在我有兩個配置類,分別是 A 和 B,如下:
@Configuration
public class A {
}
@Configuration
@Conditional(MyCondition.class)
public class B {
}A 配置類正常加載,B 配置類有一個加載條件,就是得 A 存在,B 才會加載。
現(xiàn)在,在容器中加載 B 和 A 兩個配置,如下:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(B.class,A.class);
ctx.refresh();
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}大家注意,加載的時候,我先加載了 B,后加載了 A,這點很重要,加載 B 的時候,由于此時容器中還不存在一個名為 a 的 Bean,而我們的評估時機是在處理配置類的時候,因此就會導(dǎo)致 B 配置類不會被加載,最終打印出來的 BeanName 就沒有 b。
但是,如果我們將 MyCondition 中,條件注解的評估時機改為 ConfigurationPhase.REGISTER_BEAN,那么就表示在系統(tǒng)啟動的時候,并不會去評估條件注解是否滿足,而是會將 @Configuration 配置類進行解析,此時啟動系統(tǒng),就會發(fā)現(xiàn)最終打印出來的 beanName 里既有 a 又有 b。
3. 源碼分析
接下來我們再來從源碼的角度來分析一下上述行為。
在 Spring 中,提供了一個專門的內(nèi)部類 ConditionEvaluator 來處理要不要跳過條件注解,該類中有一個名為 shouldSkip 的方法,用來處理此事:
public boolean shouldSkip(AnnotatedTypeMetadata metadata) {
return shouldSkip(metadata, null);
}
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
if (phase == null) {
if (metadata instanceof AnnotationMetadata annotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate(annotationMetadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
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;
}第一個方法不用多說,我們來看第二個重載方法,重載方法多了一個參數(shù) ConfigurationPhase,這個就表示配置的階段,也就是條件注解生效的階段。
首先會去判斷當前注解是否是一個條件注解,如果不是條件注意,那么就不能跳過,要繼續(xù)后面的解析(繼續(xù)后面的解析時 Bean 將會被注冊),如果是條件注解,則繼續(xù)后面的判斷。繼續(xù)判斷,如果沒有傳遞 phase 進來,說明沒有指定應(yīng)該在哪個階段去評估條件注解,那么這個時候就去判斷,如果當前注解是一個配置類上的注解,那么就設(shè)置 phase 為 PARSE_CONFIGURATION,然后繼續(xù)調(diào)用 shouldSkip 方法,否則就設(shè)置 phase 為 REGISTER_BEAN 然后繼續(xù)調(diào)用 shouldSkip 方法。
那么什么樣的情況會被認為是一個配置類上的注解呢?如果當前類上添加的注解時 @Component、@ComponentScan、@Import、@ImportResource 以及這四種注解衍生出來的注解,亦或者當前類中有 @Bean 注解標記的方法,那么當前類就是一個配置類,就會設(shè)置 phase 為 PARSE_CONFIGURATION。
第二次進入 shouldSkip 方法的時候,就已經(jīng)有明確的 phase 了。這次進來后,把所有的條件注解的條件收集起來,存入到 conditions 集合中,然后再對該集合進行排序。然后遍歷該集合。遍歷的時候就去判斷這個條件注解是不是 ConfigurationCondition 類型的,如果是,則提取出來其中的 phase 為 requiredPhase,這個就表示這個條件注意希望自己被處理的階段,接下來去判斷,如果 requiredPhase 為空,說明條件并未指定自己的執(zhí)行時間,那么就執(zhí)行 matches 方法進行條件評估;如果 requiredPhase 不為空,并且和傳入的 phase 相等,那么也是當前評估。其實這個判斷核心邏輯就是以參數(shù)傳入進來的 phase 為準,要么條件沒有設(shè)置評估時機,要么設(shè)置了,但是得和參數(shù)傳進來的 phase 一致,只有滿足這兩個條件,才會當場進行評估。
這就是系統(tǒng)條件注解的評估邏輯。
對于配置類來說,是在 AnnotatedBeanDefinitionReader#doRegisterBean 方法中調(diào)用評估邏輯的:
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
//...
}調(diào)用的時候并未明確指定 phase,所以會在進入到 shouldSkip 方法后,自行分析是哪個階段評估條件注解。
對于 @Bean 注解標記的類來說,是在 ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod 方法中調(diào)用評估邏輯的:
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeanMethods.add(methodName);
return;
}
//...
}這個調(diào)用的時候,就傳入了 phase 了,直接指定了是在 Bean 初始化的時候評估。
好啦,這就是條件注解條件評估時機的兩種情況。在 Spring Boot 中定義的條件注解里,有不少都用到了 ConfigurationCondition,而不是傳統(tǒng)的 Condition,感興趣的小伙伴可以自行查看哦~
到此這篇關(guān)于Spring條件注解沒生效該如何解決的文章就介紹到這了,更多相關(guān)Spring條件注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springcloud中Feign傳遞參數(shù)的過程解析
這篇文章主要介紹了Springcloud中Feign傳遞參數(shù)的過程,單個參數(shù)的傳值有兩種方式,第一種使用@RequestParam/@PathVariable進行傳值,傳遞多個參數(shù):多個參數(shù)的傳值可以使用多個@RequestParam來進行傳參,需要的朋友可以參考下2023-09-09
Springboot+rabbitmq實現(xiàn)延時隊列的兩種方式
這篇文章主要介紹了Springboot+rabbitmq實現(xiàn)延時隊列的兩種方式,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧2021-05-05
將Java(SpringBoot)項目打包為Docker鏡像的三種方法
這篇文章主要介紹了將Java(SpringBoot)項目打包為Docker鏡像的三種方法,分別是手動構(gòu)建、使用Dockerfile和使用SpringBootMaven插件,每種方法都有其特點和適用場景,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2025-03-03
使用Java代碼將IP地址轉(zhuǎn)換為int類型的方法
這篇文章主要介紹了使用Java代碼將IP地址轉(zhuǎn)換為int類型的方法,這也是各大計算機考試和ACM以及面試的常見基礎(chǔ)問題,需要的朋友可以參考下2015-08-08
基于javax.validation結(jié)合spring的最佳實踐
這篇文章主要介紹了javax.validation結(jié)合spring的最佳實踐,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
Java利用Spire.PDF實現(xiàn)將PDF文檔轉(zhuǎn)換為Word格式
在日常工作和學(xué)習中,我們經(jīng)常會遇到PDF文檔,本文將詳細介紹如何才能高效、精準地使用Java實現(xiàn)PDF到Word的自動化轉(zhuǎn)換,感興趣的小伙伴可以了解下2025-09-09

