欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring中的@Conditional注解使用和原理詳解

 更新時(shí)間:2024年01月03日 10:34:15   作者:落魄的小開發(fā)  
這篇文章主要介紹了Spring中的@Conditional注解使用和原理詳解,@Conditional在Spring4.0中被引入,用于開發(fā)"If-Then-Else"類型的bean注冊(cè)條件檢查,在@Conditional之前,也有一個(gè)注解@Porfile起到類似的作用,需要的朋友可以參考下

前言

熟悉 SpringBoot 的小伙伴們肯定不會(huì)對(duì) @Conditional 注解感到陌生,它在 SpringBoot 的自動(dòng)化配置特性中起到了非常重要的作用。

許多配置類在加載 Bean 時(shí)都使用到了 @ConditionalOnClass、@ConditionalOnBean,@ConditionalOnProperty 等 @Conditional 的衍生注解。

那么,在單純的 Spring 項(xiàng)目中,我們是否也可以使用 @Conditional 來(lái)實(shí)現(xiàn)一些自動(dòng)化配置的特性呢?

我們?cè)撛趺礃尤ナ褂聾Conditional? 它又是如何生效的?

別著急,本篇文章會(huì)一一解答。

概述

@Conditional 在 Spring 4.0 中被引入,用于開發(fā) “If-Then-Else” 類型的 bean 注冊(cè)條件檢查。在 @Conditional 之前,也有一個(gè)注解 @Porfile 起到類似的作用,它們兩個(gè)的區(qū)別在于:

  • @Profile 僅用于基于環(huán)境變量的條件檢查,使用范圍比較窄。
  • @Conditional 更加通用,開發(fā)人員可以自定義條件檢查策略??捎糜?bean 注冊(cè)時(shí)的條件檢查。
  • 4.3.8后,@Profile 也基于 @Conditional 來(lái)實(shí)現(xiàn)。

用法

首先來(lái)看一下源碼中 @Conditional 的定義

package org.springframework.context.annotation;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
	Class<? extends Condition>[] value();
}

根據(jù)定義, @Conditional 可以使用在類或方法上,具體的用法有:

  • 作為類注解,標(biāo)注在直接或間接使用了 @Component 的類上,包括 @Configuration 類
  • 作為元注解,直接標(biāo)注在其他的注解上面,用于編寫自定義注解
  • 作為任何 @Bean 方法的方法級(jí)注解

@Conditional 有一個(gè)屬性 value,其類型是 Condition 數(shù)組。組件必須匹配數(shù)組中所有的 Condition,才可以被注冊(cè)。

package org.springframework.context.annotation;
@FunctionalInterface
public interface Condition {
    /**
	 * 判斷條件是否匹配
	 * @param context 上下文信息,可以從中獲取 BeanDefinitionRegistry,BeanFactory,Environment,ResourceLoader,ClassLoader 等一些用于資源加載的信息
	 * @param metadata 注解的元信息,可以從中獲取注解的屬性
	 * @return {@code true} 條件匹配,組件可以注冊(cè)
	 * or {@code false} 否決組件的注冊(cè)
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

Condition 是一個(gè)函數(shù)式接口,只有一個(gè) matches 方法,返回 true 則表示條件匹配。matches 方法的兩個(gè)參數(shù)分別是上下文信息和注解的元信息,從這兩個(gè)參數(shù)中可以獲取到 IOC 容器和當(dāng)前組件的信息,從而判斷條件是否匹配。 由于 ConditionContext 和 AnnotatedTypeMetadata 的方法都比較簡(jiǎn)單,這里就不貼出源碼了,有興趣的小伙伴可自行翻看源碼。 Condition 必須遵循與 BeanFactoryPostProcessor 相同的限制,并注意永遠(yuǎn)不要與 bean 實(shí)例交互。如果要對(duì)與 @Configuration bean 交互的條件進(jìn)行更細(xì)粒度的控制,可以考慮 ConfigurationCondition 接口。

public interface ConfigurationCondition extends Condition {
    /**
	 * 返回條件生效的階段
	 */
    ConfigurationPhase getConfigurationPhase();
    enum ConfigurationPhase {
        /**
		 * 在 @Configuration 類解析時(shí)生效
		 */
        PARSE_CONFIGURATION,
        /**
		 * 在 bean 注冊(cè)時(shí)生效。此時(shí)所有的 @Configuration 都解析完成了。
		 */
        REGISTER_BEAN
        }
}

接下來(lái)我們?cè)?Spring 下實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 ConditionalOnBean 注解,實(shí)現(xiàn)一個(gè) bean 只有在另一個(gè) bean 存在時(shí),才進(jìn)行注冊(cè)。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
// Conditional 作為元注解,主要的判斷邏輯在 OnBeanCondition 類中
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
    // bean 的名稱
    String[] name() default {};
}
// OnBeanCondition 主要的判斷邏輯在 matches 方法中
class OnBeanCondition implements ConfigurationCondition {
    @Override
	public ConfigurationPhase getConfigurationPhase() {
		return ConfigurationPhase.REGISTER_BEAN;
	}
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
            MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(ConditionalOnShardingProps.class.getName());
            if (attrs != null) {
                ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
                for (Object beanName : attrs.get("name")) {
                    if(!beanFactory.containsBean((String) beanName)) {
                        return false;
                    }
                }
                return true;
            }
        }
        return true;
    }
}
// 使用 ConditionalOnBean 注解
@Configuration
@Conditional(ConditionalOnBean.class)
public static class OnBeanConfig {
    @Bean
    @ConditionalOnBean(name = "a")
    public B b() {
        return new B();
    }
}

這樣,一個(gè)自定義的 Conditional 注解就寫好了,使用時(shí)只要把它加到類或方法上即可生效。

原理

首先,通過(guò)調(diào)用鏈路的分析可知,Conditional 的調(diào)用方是 ConditionEvaluator,而 ConditionEvaluator 在 ConfigurationClassParser、ConfigurationClassBeanDefinitionReader 和 AnnotatedBeanDefinitionReader 中均有所使用。先來(lái)看下這三個(gè)類在 Spring 的流程中扮演什么角色。 ConfigurationClassParser ConfigurationClassParser 在 ConfigurationClassPostProcessor 中被使用到,而 ConfigurationClassPostProcessor 是一個(gè) BeanDefinitionRegistryPostProcessor,顧名思義,就是在 bean 掃描完成后,對(duì) bean 的定義進(jìn)行修改的一個(gè)后置處理器,主要的功能在于解析 bean 中的所有配置類。

// ConfigurationClassParser 的核心邏輯
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    // 調(diào)用 shouldSkip 方法,對(duì)應(yīng)的階段是 PARSE_CONFIGURATION
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
	}
    ... 省略
}

ConfigurationClassBeanDefinitionReader

ConfigurationClassBeanDefinitionReader 也是在 ConfigurationClassPostProcessor 中被使用到。在配置類解析完成后,對(duì)其中包含的 bean 進(jìn)行注冊(cè)。

private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    // 調(diào)用的還是 conditionEvaluator.shouldSkip,在其基礎(chǔ)上做了個(gè)緩存。
    // 對(duì)應(yīng)的階段是 REGISTER_BEAN
    if (trackedConditionEvaluator.shouldSkip(configClass)) {
        String beanName = configClass.getBeanName();
        if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
            this.registry.removeBeanDefinition(beanName);
        }
        this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
        return;
    }
    ... 省略
}

AnnotatedBeanDefinitionReader

AnnotatedBeanDefinitionReader 主要在 AnnotationConfigApplicationContext 中被使用到。AnnotationConfigApplicationContext 是 Spring 中的一個(gè)高級(jí)容器,與 ClassPathXmlApplicationContext 不同的是,它主要通過(guò)解析 Java 配置文件中的配置,來(lái)進(jìn)行 bean 的注冊(cè)。

<T> void doRegisterBean(Class<T> beanClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name, @Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {
    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
    // 調(diào)用 shouldSkip 方法,對(duì)應(yīng)的階段為 null
    if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
        return;
    }
    ... 省略
}

綜上,我們已經(jīng)了解了 ConditionEvaluator 在 Spring 的流程中是如何發(fā)揮作用的,接下來(lái)看看核心方法 shouldSkip 的具體實(shí)現(xiàn)邏輯。

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    // 不存在 Conditional 注解,則不處理
    if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
        return false;
    }
    // 階段為空時(shí)的處理邏輯
    if (phase == null) {
        // 有 Configuration、Component、ComponentScan、Import、ImportResource 等注解,則任務(wù)是配置解析階段
        if (metadata instanceof AnnotationMetadata &&
            ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
            return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
        }
        return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
    }
    // 獲取所有 Conditional 注解,并提取出 Condition 類
    List<Condition> conditions = new ArrayList<>();
    for (String[] conditionClasses : getConditionClasses(metadata)) {
        for (String conditionClass : conditionClasses) {
            Condition condition = getCondition(conditionClass, this.context.getClassLoader());
            conditions.add(condition);
        }
    }
    // 對(duì) Condition 進(jìn)行排序
    AnnotationAwareOrderComparator.sort(conditions);
    for (Condition condition : conditions) {
        ConfigurationPhase requiredPhase = null;
        if (condition instanceof ConfigurationCondition) {
            requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
        }
        // 調(diào)用 Condition 的 matches 方法,不符合條件的則跳過(guò)
        if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
            return true;
        }
    }
    // 所有的 Condition 都符合,則不跳過(guò),進(jìn)行后續(xù)處理
    return false;
}

看了源代碼,相信小伙伴們對(duì) Conditional 的理解又深入了一層。

  • Conditional 可以用作元注解加在自定義注解之上。
  • Spring 在解析配置類或者注冊(cè) bean 時(shí),都會(huì)調(diào)用 ConditionEvaluator#shouldSkip 方法,判斷是否符合注冊(cè)條件。
  • shouldSkip 會(huì)獲取到組件上的所有 Conditional 注解,并拿到注解上的所有 Condition 類,調(diào)用 Condition#matches 進(jìn)行判斷。
  • Condition 默認(rèn)按照定義的順序來(lái)執(zhí)行,一般通過(guò) @Order 對(duì)Condition 進(jìn)行排序。
  • 只有所有條件都符合,Spring 才會(huì)進(jìn)行后續(xù)的處理流程。

總結(jié)

  • @Conditional 注解用于開發(fā) “If-Then-Else” 類型的 bean 注冊(cè)條件檢查。
  • @Conditional 可以使用在類或方法上,具體的用法有三種:
    • 作為類注解,標(biāo)注在直接或間接使用了 @Component 的類上,包括 @Configuration 類
    • 作為元注解,直接標(biāo)注在其他的注解上面,用于編寫自定義注解
    • 作為任何 @Bean 方法的方法級(jí)注解
  • @Conditional 在解析配置類和注冊(cè) bean 這兩個(gè)階段生效??梢酝ㄟ^(guò) ConfigurationCondition 指定階段。
  • Condition 默認(rèn)按照定義的順序來(lái)執(zhí)行,一般通過(guò) @Order 對(duì)Condition 進(jìn)行排序。
  • 組件必須匹配所有的 Condition,才可以被注冊(cè)。

到此這篇關(guān)于Spring中的@Conditional注解使用和原理詳解的文章就介紹到這了,更多相關(guān)Spring中的@Conditional注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論