Spring Boot 源碼分析 - Condition 接口的擴展


參考 知識星球芋道源碼 星球的源碼解析,一個活躍度非常高的 Java 技術社群,感興趣的小伙伴可以加入 芋道源碼 星球,一起學習😄

該系列文章是筆者在學習 Spring Boot 過程中總結下來的,里面涉及到相關源碼,可能對讀者不太友好,請結合我的源碼注釋 Spring Boot 源碼分析 GitHub 地址 進行閱讀

Spring Boot 版本:2.2.x

最好對 Spring 源碼有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 - 文章導讀》 系列文章

如果該篇內容對您有幫助,麻煩點擊一下“推薦”,也可以關注博主,感激不盡~

該系列其他文章請查看:《精盡 Spring Boot 源碼分析 - 文章導讀》

概述

在上一篇《剖析 @SpringBootApplication 注解》文章分析了 Spring Boot 的自動配置功能,在通過 @EnableAutoConfiguration 注解驅動整個自動配置模塊的過程中,並不是所有的自動配置類都需要被注入,不同的自動配置類需要滿足一定條件后,才應該進行自動配置

那么 Spring Boot 怎么知道滿足一定條件呢?Spring Boot 對 Spring 的 Condition 接口進行了擴展,然后結合自定義的注解,則可以判斷自動配置類是否符合條件。

例如 @ConditionalOnClass 可以指定必須存在哪些 Class 對象才注入這個 Bean。

那么接下來,我們一起來看看 Spring 的 Condition 接口以及 Spring Boot 對其的擴展

Condition 演進史

Profile 的出場

在 Spring 3.1 的版本,為了滿足不同環境注冊不同的 Bean ,引入了 @Profile 注解。例如:

@Configuration
public class DataSourceConfiguration {

    @Bean
    @Profile("DEV")
    public DataSource devDataSource() {
        // ... 單機 MySQL
    }

    @Bean
    @Profile("PROD")
    public DataSource prodDataSource() {
        // ... 集群 MySQL
    }
}
  • 在測試環境下,我們注冊單機 MySQL 的 DataSource Bean
  • 在生產環境下,我們注冊集群 MySQL 的 DataSource Bean

Spring 3.1.x 的 @Profile 注解如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Profile {

	/**
	 * The set of profiles for which this component should be registered.
	 */
	String[] value();
}

可以看到,最開始 @Profile 注解並沒有結合 @Conditional 注解一起使用,而是在后續版本才引入的

Condition 的出現

在 Spring 4.0 的版本,出現了 Condition 功能,體現在 org.springframework.context.annotation.Condition 接口,如下:

@FunctionalInterface
public interface Condition {

	/**
	 * Determine if the condition matches.
	 * @param context the condition context
	 * @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
	 * or {@link org.springframework.core.type.MethodMetadata method} being checked
	 * @return {@code true} if the condition matches and the component can be registered,
	 * or {@code false} to veto the annotated component's registration
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

函數式接口,只有一個 matches(..) 方法,判斷是否匹配,從入參中可以知道,它是和注解配合一起實現 Condition 功能的,也就是 @Conditional 注解,如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition} classes that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();
}

隨之 @Profile 注解也進行了修改,和 @Conditional 注解配合使用

Spring 5.1.x 的 @Profile 注解如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {

	/**
	 * The set of profiles for which the annotated component should be registered.
	 */
	String[] value();
}

這里指定的的 Condition 實現類是 ProfileCondition,如下:

class ProfileCondition implements Condition {

	@Override
	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;
	}
}

邏輯很簡答,從當前 Spring 應用上下文的 Environment 中判斷 @Profile 指定的環境是否被激活,被激活了表示匹配成功,則注入對應的 Bean,否則,不進行操作

但是 Spring 本身提供的 Condition 實現類不多,只有一個 ProfileCondition 對象

SpringBootCondition 的進擊

Spring Boot 為了滿足更加豐富的 Condition 場景,對 Spring 的 Condition 接口進行了擴展,提供更多的實現類,如下:

上面僅列出了部分 SpringBootCondition 的子類,同時這些子類與對應的注解配置一起使用

  • @ConditionalOnClass:必須都存在指定的 Class 對象們
  • @ConditionalOnMissingClass:指定的 Class 對象們必須都不存在
  • @ConditionalOnBean:必須都存在指定的 Bean 們
  • @ConditionalOnMissingBean:指定的 Bean 們必須都不存在
  • @ConditionalOnSingleCandidate:必須存在指定的 Bean
  • @ConditionalOnProperty:指定的屬性是否有指定的值
  • @ConditionalOnWebApplication:當前的 WEB 應用類型是否在指定的范圍內(ANY、SERVLET、REACTIVE)
  • @ConditionalOnNotWebApplication:不是 WEB 應用類型

上面列出了 Spring Boot 中常見的幾種 @ConditionXxx 注解,他們都配合 @Conditional 注解與對應的 Condition 實現類一起使用,提供了非常豐富的 Condition 場景

Condition 在哪生效?

Spring 提供了 Condition 接口以及 @Conditional 注解,那么在 Spring 中哪里體現,或者說是哪里進行判斷的呢?

其實我在 《死磕Spring之IoC篇 - @Bean 等注解的實現原理》 這篇文章中有提到過,我們稍微回顧一下,有兩種情況:

  • 通過 @Component 注解(及派生注解)標注的 Bean
  • @Configuration 標注的配置類中的 @Bean 標注的方法 Bean

普通 Bean

第一種情況是在 Spring 掃描指定路徑下的 .class 文件解析成對應的 BeanDefinition(Bean 的前身)時,會根據 @Conditional 注解判斷是否符合條件,如下:

// ClassPathBeanDefinitionScanner.java
public int scan(String... basePackages) {
    // <1> 獲取掃描前的 BeanDefinition 數量
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

    // <2> 進行掃描,將過濾出來的所有的 .class 文件生成對應的 BeanDefinition 並注冊
    doScan(basePackages);

    // Register annotation config processors, if necessary.
    // <3> 如果 `includeAnnotationConfig` 為 `true`(默認),則注冊幾個關於注解的 PostProcessor 處理器(關鍵)
    // 在其他地方也會注冊,內部會進行判斷,已注冊的處理器不會再注冊
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

    // <4> 返回本次掃描注冊的 BeanDefinition 數量
    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
// ClassPathScanningCandidateComponentProvider.java
private boolean isConditionMatch(MetadataReader metadataReader) {
    if (this.conditionEvaluator == null) {
        this.conditionEvaluator =
                new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
    }
    return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
}

上面只是簡單的提一下,可以看到會通過 ConditionEvaluator 計算器進行計算,判斷是否滿足條件

配置類

第二種情況是 Spring 會對 配置類進行處理,掃描到帶有 @Bean 注解的方法,嘗試解析成 BeanDefinition(Bean 的前身)時,會根據 @Conditional 注解判斷是否符合條件,如下:

// ConfigurationClassBeanDefinitionReader.java
private void loadBeanDefinitionsForConfigurationClass(
        ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

    // <1> 如果不符合 @Conditional 注解的條件,則跳過
    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;
    }

    // <2> 如果當前 ConfigurationClass 是通過 @Import 注解被導入的
    if (configClass.isImported()) {
        // <2.1> 根據該 ConfigurationClass 對象生成一個 BeanDefinition 並注冊
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    // <3> 遍歷當前 ConfigurationClass 中所有的 @Bean 注解標注的方法
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
        // <3.1> 根據該 BeanMethod 對象生成一個 BeanDefinition 並注冊(注意這里有無 static 修飾會有不同的配置)
        loadBeanDefinitionsForBeanMethod(beanMethod);
    }

    // <4> 對 @ImportResource 注解配置的資源進行處理,對里面的配置進行解析並注冊 BeanDefinition
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    // <5> 通過 @Import 注解導入的 ImportBeanDefinitionRegistrar 實現類往 BeanDefinitionRegistry 注冊 BeanDefinition
    // Mybatis 集成 Spring 就是基於這個實現的,可查看 Mybatis-Spring 項目中的 MapperScannerRegistrar 這個類
    // https://github.com/liu844869663/mybatis-spring/blob/master/src/main/java/org/mybatis/spring/annotation/MapperScannerRegistrar.java
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

上面只是簡單的提一下,可以看到會通過 TrackedConditionEvaluator 計算器進行計算,判斷是否滿足條件

這里提一下,對於 @Bean 標注的方法,會得到 CGLIB 的提升,也就是返回的是一個代理對象,設置一個攔截器專門對 @Bean 方法進行攔截處理,通過依賴查找的方式從 IoC 容器中獲取 Bean 對象,如果是單例 Bean,那么每次都是返回同一個對象,所以當主動調用這個方法時獲取到的都是同一個對象。

SpringBootCondition

org.springframework.boot.autoconfigure.condition.SpringBootCondition 抽象類,實現了 Condition 接口,Spring Boot 擴展 Condition 的抽象基類,主要用於打印相應的日志,並記錄每次的匹配結果,如下:

/**
 * Base of all {@link Condition} implementations used with Spring Boot. Provides sensible
 * logging to help the user diagnose what classes are loaded.
 *
 * @author Phillip Webb
 * @author Greg Turnquist
 * @since 1.0.0
 */
public abstract class SpringBootCondition implements Condition {

	private final Log logger = LogFactory.getLog(getClass());

	@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		// <1> 從注解元信息中獲取所標注的`類名`(或者`類名#方法名`)
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
			// <2> 獲取匹配結果(包含匹配消息),抽象方法,交由子類實現
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			// <3> 打印匹配日志
			logOutcome(classOrMethodName, outcome);
			// <4> 向 ConditionEvaluationReport 中記錄本次的匹配結果
			recordEvaluation(context, classOrMethodName, outcome);
			// <5> 返回匹配結果
			return outcome.isMatch();
		}
		catch (NoClassDefFoundError ex) {
			// 拋出異常
		} catch (RuntimeException ex) {
			// 拋出異常
		}
	}
}

實現的 Condition 接口方法處理過程如下:

  1. 從注解元信息中獲取所標注的類名(或者類名#方法名

    private static String getClassOrMethodName(AnnotatedTypeMetadata metadata) {
        if (metadata instanceof ClassMetadata) {
            ClassMetadata classMetadata = (ClassMetadata) metadata;
            return classMetadata.getClassName();
        }
        MethodMetadata methodMetadata = (MethodMetadata) metadata;
        return methodMetadata.getDeclaringClassName() + "#" + methodMetadata.getMethodName();
    }
    
  2. 調用 getMatchOutcome(..) 方法,獲取匹配結果(包含匹配消息),抽象方法,交由子類實現

  3. 打印匹配日志

    protected final void logOutcome(String classOrMethodName, ConditionOutcome outcome) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(getLogMessage(classOrMethodName, outcome));
        }
    }
    
  4. 向 ConditionEvaluationReport 中記錄本次的匹配結果

    private void recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome) {
        if (context.getBeanFactory() != null) {
            ConditionEvaluationReport.get(context.getBeanFactory()).recordConditionEvaluation(classOrMethodName, this, outcome);
        }
    }
    
  5. 返回匹配結果

SpringBootCondition 的實現類

OnClassCondition

org.springframework.boot.autoconfigure.condition.OnClassCondition,繼承 SpringBootCondition 抽象類,如下:

@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
    /**
	 * 該方法來自 {@link SpringBootCondition} 判斷某個 Bean 是否符合注入條件(`@ConditionalOnClass` 和 `ConditionalOnMissingClass`)
	 */
	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		ClassLoader classLoader = context.getClassLoader();
		ConditionMessage matchMessage = ConditionMessage.empty();
		// <1> 獲取這個類上面的 `@ConditionalOnClass` 注解的值
		// 也就是哪些 Class 對象必須存在
		List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
		if (onClasses != null) {
			// <1.1> 找到這些 Class 對象中哪些是不存在的
			List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
			// <1.2> 如果存在不存在的,那么不符合條件,返回不匹配
			if (!missing.isEmpty()) {
				return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
						.didNotFind("required class", "required classes").items(Style.QUOTE, missing));
			}
			// <1.3> 添加 `@ConditionalOnClass` 滿足條件的匹配信息
			matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
					.found("required class", "required classes")
					.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
		}
		// <2> 獲取這個類上面的 `@ConditionalOnMissingClass` 注解的值
		// 也就是這些 Class 對象必須都不存在
		List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
		if (onMissingClasses != null) {
			// <2.1> 找到這些 Class 對象中哪些是存在的
			List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
			// <2.2> 如果有一個存在,那么不符合條件,返回不匹配
			if (!present.isEmpty()) {
				return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
						.found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
			}
			// <2.3> 添加 `@ConditionalOnMissingClass` 滿足條件的匹配信息
			matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
					.didNotFind("unwanted class", "unwanted classes")
					.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
		}
		// <3> 返回符合條件的結果
		return ConditionOutcome.match(matchMessage);
	}
}

判斷是否匹配的過程如下:

  1. 獲取這個類上面的 @ConditionalOnClass 注解的值,也就是哪些 Class 對象必須存在

    1. 找到這些 Class 對象中哪些是不存在的

      protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,
              ClassLoader classLoader) {
          // 如果為空,則返回空結果
          if (CollectionUtils.isEmpty(classNames)) {
              return Collections.emptyList();
          }
          List<String> matches = new ArrayList<>(classNames.size());
          // 使用 `classNameFilter` 對 `classNames` 進行過濾
          for (String candidate : classNames) {
              if (classNameFilter.matches(candidate, classLoader)) {
                  matches.add(candidate);
              }
          }
          // 返回匹配成功的 `className` 們
          return matches;
      }
      
    2. 如果存在不存在的,那么不符合條件,返回不匹配

    3. 添加 @ConditionalOnClass 滿足條件的匹配信息

  2. 獲取這個類上面的 @ConditionalOnMissingClass 注解的值,也就是這些 Class 對象必須都不存在

    1. 找到這些 Class 對象中哪些是存在的,和上面的 1.1 差不多,只不過這里傳的是 ClassNameFilter.PRESENT 過濾器
    2. 如果有一個存在,那么不符合條件,返回不匹配
    3. 添加 @ConditionalOnMissingClass 滿足條件的匹配信息
  3. 返回符合條件的結果

上面使用到的 ClassNameFilter 如下:

protected enum ClassNameFilter {

    /** 指定類存在 */
    PRESENT {
        @Override
        public boolean matches(String className, ClassLoader classLoader) {
            return isPresent(className, classLoader);
        }
    },

    /** 指定類不存在 */
    MISSING {
        @Override
        public boolean matches(String className, ClassLoader classLoader) {
            return !isPresent(className, classLoader);
        }
    };

    abstract boolean matches(String className, ClassLoader classLoader);

    static boolean isPresent(String className, ClassLoader classLoader) {
        if (classLoader == null) {
            classLoader = ClassUtils.getDefaultClassLoader();
        }
        try {
            // 加載指定類,加載成功表示存在這個類
            resolve(className, classLoader);
            return true;
        }
        catch (Throwable ex) {
            // 加載失敗表示不存在這個類
            return false;
        }
    }
}

邏輯很簡單,就是判斷 Class 對象是否存在或者不存在

其它實現類

關於 SpringBootCondition 其他的實現類邏輯都差不多,感興趣的可以去看看

回顧自動配置

在上一篇《剖析 @SpringBootApplication 注解》 文章分析通過 @EnableAutoConfiguration 注解驅動整個自動配置模塊的過程中,會通過指定的 AutoConfigurationImportFilter 對所有的自動配置類進行過濾,滿足條件才進行自動配置

可以回顧一下上一篇文章的 2. getAutoConfigurationEntry 方法 小節和 3. filter 方法 小節

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    // <1> 如果通過 `spring.boot.enableautoconfiguration` 配置關閉了自動配置功能
    if (!isEnabled(annotationMetadata)) {
        // 則返回一個“空”的對象
        return EMPTY_ENTRY;
    }
    // <2> 獲取 `@EnableAutoConfiguration` 注解的配置信息
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // <3> 從所有的 `META-INF/spring.factories` 文件中找到 `@EnableAutoConfiguration` 注解對應的類(需要自動配置的類)
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // <4> 對所有的自動配置類進行去重
    configurations = removeDuplicates(configurations);
    // <5> 獲取需要排除的自動配置類
    // 可通過 `@EnableAutoConfiguration` 注解的 `exclude` 和 `excludeName` 配置
    // 也可以通過 `spring.autoconfigure.exclude` 配置
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // <6> 處理 `exclusions` 中特殊的類名稱,保證能夠排除它
    checkExcludedClasses(configurations, exclusions);
    // <7> 從 `configurations` 中將 `exclusions` 需要排除的自動配置類移除
    configurations.removeAll(exclusions);
    /**
     * <8> 從 `META-INF/spring.factories` 找到所有的 {@link AutoConfigurationImportFilter} 對 `configurations` 進行過濾處理
     * 例如 Spring Boot 中配置了 {@link org.springframework.boot.autoconfigure.condition.OnClassCondition}
     * 在這里提前過濾掉一些不滿足條件的自動配置類,在 Spring 注入 Bean 的時候也會判斷哦~
     */
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // <10> 將所有的自動配置類封裝成一個 AutoConfigurationEntry 對象,並返回
    return new AutoConfigurationEntry(configurations, exclusions);
}

我們看到第 8 步,調用 filter(..) 方法, 目的就是過濾掉一些不符合 Condition 條件的自動配置類

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    // <1> 將自動配置類保存至 `candidates` 數組中
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    /*
     * <2> 從 `META-INF/spring.factories` 找到所有的 AutoConfigurationImportFilter 對 `candidates` 進行過濾處理
     * 有 OnClassCondition、OnBeanCondition、OnWebApplicationCondition
     */
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        // <2.1> Aware 回調
        invokeAwareMethods(filter);
        // <2.2> 對 `candidates` 進行匹配處理,獲取所有的匹配結果
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        // <2.3> 遍歷匹配結果,將不匹配的自動配置類至空
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                skip[i] = true;
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    // <3> 如果沒有不匹配的結果則全部返回
    if (!skipped) {
        return configurations;
    }
    // <4> 獲取到所有匹配的自動配置類,並返回
    List<String> result = new ArrayList<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }
    return new ArrayList<>(result);
}

可以看到第 2 步,會從 META-INF/spring.factories 中找到對應的 AutoConfigurationImportFilter 實現類對所有的自動配置類進行過濾

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

這里,我們一定要注意到入參中的 AutoConfigurationMetadata 對象,它里面保存了 META-INF/spring-autoconfigure-metadata.properties 文件中 Spring Boot 的自動配置類的注解元信息(Sprng Boot 編譯時生成的),如何來的請回顧上一篇文章

AutoConfigurationImportFilter

org.springframework.boot.autoconfigure.AutoConfigurationImportFilter 接口,用於過濾掉無需自動引入的自動配置類

/**
 * Filter that can be registered in {@code spring.factories} to limit the
 * auto-configuration classes considered. This interface is designed to allow fast removal
 * of auto-configuration classes before their bytecode is even read.
 *
 * @author Phillip Webb
 * @since 1.5.0
 */
@FunctionalInterface
public interface AutoConfigurationImportFilter {

	boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);
}

可以看到它的注釋,因為自動配置類會很多,如果無需使用,而創建對應的 Bean(字節碼)到 JVM 內存中,將是一種浪費

可以看到它的最終實現類,都是構建在 SpringBootCondition 之上。😈 不過這也很正常,因為 Condition 本身提供的一個功能,就是作為配置類(Configuration)是否能夠符合條件被引入。

FilteringSpringBootCondition

org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition,繼承 SpringBootCondition 抽象類,實現 AutoConfigurationImportFilter 接口,作為具有 AutoConfigurationImportFilter 功能的 SpringBootCondition 的抽象基類。

abstract class FilteringSpringBootCondition extends SpringBootCondition
		implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
	/**
	 * 底層 IoC 容器
	 */
	private BeanFactory beanFactory;
	/**
	 * 類加載器
	 */
	private ClassLoader beanClassLoader;

	@Override
	public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
		// <1> 從 Spring 應用上下文中獲取 ConditionEvaluationReport 對象
		ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
		// <2> 獲取所有自動配置類的匹配結果,空方法,交由子類實現
		ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
		// <3> 將自動配置類的匹配結果保存至一個 `boolean[]` 數組中,並將匹配結果一一保存至 ConditionEvaluationReport 中
		boolean[] match = new boolean[outcomes.length];
		for (int i = 0; i < outcomes.length; i++) {
			// 注意這里匹配結果為空也表示匹配成功
			match[i] = (outcomes[i] == null || outcomes[i].isMatch());
			if (!match[i] && outcomes[i] != null) {
				logOutcome(autoConfigurationClasses[i], outcomes[i]);
				if (report != null) {
					report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
				}
			}
		}
		// <4> 返回所有自動配置類是否滿足條件的結果數組
		return match;
	}
}

可以看到,這實現方法和 SpringBootCondition 的 match(..) 方法很想,他們的入參不同,不要搞混了,這里的處理過程如下:

  1. 從 Spring 應用上下文中獲取 ConditionEvaluationReport 對象
  2. 調用 getOutcomes(..) 抽象方法,獲取所有自動配置類的匹配結果,空方法,交由子類實現
  3. 將自動配置類的匹配結果保存至一個 boolean[] 數組中,並將匹配結果一一保存至 ConditionEvaluationReport 中
    • 注意這里匹配結果為空也表示匹配成功
  4. 返回所有自動配置類是否滿足條件的結果數組

FilteringSpringBootCondition 的實現類

OnClassCondition

org.springframework.boot.autoconfigure.condition.OnClassCondition,繼承 FilteringSpringBootCondition 抽象類,如下:

@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {

	/**
	 * 該方法來自 {@link AutoConfigurationImportFilter} 判斷這些自動配置類是否符合條件(`@ConditionalOnClass`)
	 */
	@Override
	protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
			AutoConfigurationMetadata autoConfigurationMetadata) {
		// Split the work and perform half in a background thread if more than one
		// processor is available. Using a single additional thread seems to offer the
		// best performance. More threads make things worse.
		// 考慮到自動配置類上面的 `@Conditional` 相關注解比較多,所以采用多線程以提升效率。經過測試使用,使用兩個線程的效率是最高的,
		// 所以會將 `autoConfigurationClasses` 一分為二進行處理

		// <1> 如果 JVM 可用的處理器不止一個,那么這里用兩個線程去處理
		if (Runtime.getRuntime().availableProcessors() > 1) {
			// <1.1> 對 `autoConfigurationClasses` 所有的自動配置類進行處理
			// 這里是對 `@ConditionalOnClass` 注解進行處理,必須存在指定 Class 類對象
			return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
		}
		// <2> 否則,就是單核處理,當前線程去處理
		else {
			// <2.1> 創建一個匹配處理器 `outcomesResolver`
			OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, 0,
					autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
			// <2.2> 返回 `outcomesResolver` 的執行結果
			// 這里是對 `@ConditionalOnClass` 注解進行處理,必須存在指定 Class 類對象
			return outcomesResolver.resolveOutcomes();
		}
	}
}

過濾所有自動配置類的的過程如下:

考慮到自動配置類上面的 @Conditional 相關注解比較多,所以采用多線程以提升效率。經過測試使用,使用兩個線程的效率是最高的,所以會嘗試將 autoConfigurationClasses 一分為二進行處理

  1. 如果 JVM 可用的處理器不止一個,那么這里用兩個線程去處理
    1. autoConfigurationClasses 所有的自動配置類進行處理,這里是對 @ConditionalOnClass 注解進行處理,必須存在指定 Class 類對象
  2. 否則,就是單核處理,當前線程去處理
    1. 創建一個匹配處理器 outcomesResolver
    2. 返回 outcomesResolver 的執行結果,這里是對 @ConditionalOnClass 注解進行處理,必須存在指定 Class 類對象
resolveOutcomesThreaded 方法
private ConditionOutcome[] resolveOutcomesThreaded(String[] autoConfigurationClasses,
        AutoConfigurationMetadata autoConfigurationMetadata) {
    // <1> 將自動配置類的個數一分為二
    int split = autoConfigurationClasses.length / 2;
    // <2> 創建一個 StandardOutcomesResolver 匹配處理器,另起一個線程去處理前一半的自動配置類
    OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,
            autoConfigurationMetadata);
    // <3> 創建一個 StandardOutcomesResolver 匹配處理器,當前線程去處理后一半的自動配置類
    OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
            autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
    // <4> 獲取兩個匹配器處理器的處理結果,將他們合並,然后返回
    ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
    ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
    ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
    System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
    System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
    return outcomes;
}

邏輯很簡單,就是同時兩個線程分半處理,還是通過 StandardOutcomesResolver 匹配處理器來處理

StandardOutcomesResolver 處理器

private final class StandardOutcomesResolver implements OutcomesResolver {
    /**
     * 需要處理的自動配置類
     */
    private final String[] autoConfigurationClasses;
    /**
     * 區間開始位置
     */
    private final int start;
    /**
     * 區間結束位置
     */
    private final int end;
    /**
     * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration} 注解的元信息
     */
    private final AutoConfigurationMetadata autoConfigurationMetadata;
    /**
     * 類加載器
     */
    private final ClassLoader beanClassLoader;

    @Override
    public ConditionOutcome[] resolveOutcomes() {
        // 獲取自動配置類的匹配結果
        return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
    }
}
getOutcomes 方法
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,
        AutoConfigurationMetadata autoConfigurationMetadata) {
    ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
    /**
     * 遍歷執行區間內的自動配置類
     */
    for (int i = start; i < end; i++) {
        String autoConfigurationClass = autoConfigurationClasses[i];
        if (autoConfigurationClass != null) {
            /**
             * 獲取這個自動配置類的 `@ConditionalOnClass` 注解的值
             * 這里不需要解析,而是從一個 `Properties` 中直接獲取
             * 參考 {@link AutoConfigurationImportSelector} 中我的注釋
             * 參考 {@link AutoConfigureAnnotationProcessor}
             */
            String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
            // 如果值不為空,那么這里先進行匹配處理,判斷這個自動配置類是否需要注入,也就是是否存在 指定的 Class 對象(`candidates`)
            // 否則,不進行任何處理,也就是不過濾掉
            if (candidates != null) {
                // 判斷指定的 Class 類對象是否都存在,都存在返回 `null`,有一個不存在返回不匹配
                outcomes[i - start] = getOutcome(candidates);
            }
        }
    }
    return outcomes;
}

private ConditionOutcome getOutcome(String candidates) {
    try {

        // 這個配置類的 `@ConditionalOnClass` 注解只指定了一個,則直接處理
        if (!candidates.contains(",")) {
            // 判斷這個 Class 類對象是否存在,存在返回 `null`,不存在返回不匹配
            return getOutcome(candidates, this.beanClassLoader);
        }
        // 這個配置類的 `@ConditionalOnClass` 注解指定了多個,則遍歷處理,必須都存在
        for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {
            // 判斷這個 Class 類對象是否存在,存在返回 `null`,不存在返回不匹配
            ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader);
            // 如果不為空,表示不匹配,直接返回
            if (outcome != null) {
                return outcome;
            }
        }
    }
    catch (Exception ex) { }
    return null;
}

private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
    // 如果這個 Class 對象不存在,則返回不匹配
    if (ClassNameFilter.MISSING.matches(className, classLoader)) {
        return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
                .didNotFind("required class").items(Style.QUOTE, className));
    }
    return null;
}

邏輯比較簡單,先從 AutoConfigurationMetadata 這個對象中找到這個自動配置類 @ConditionalOnClass 注解的值,如下:

@Override
public String get(String className, String key) {
    return get(className, key, null);
}

@Override
public String get(String className, String key, String defaultValue) {
    // 獲取 `類名.注解簡稱` 對應的值,也就是這個類上面該注解的值
    String value = this.properties.getProperty(className + "." + key);
    // 如果存在該注解的值,則返回,沒有的話返回指定的默認值
    return (value != null) ? value : defaultValue;
}

如果沒有該注解,匹配結果為空,也表示匹配成功,存在該注解,則對指定的 Class 對象進行判斷,如果有一個 Class 對象不存在,匹配結果則是不匹配,所以這個 AutoConfigurationMetadata 對於過濾自動配置類很關鍵

其它實現類

另外兩個 OnBeanCondition 和 OnWebApplicationCondition 實現類的原理差不多,感興趣的可以去看看

AutoConfigurationMetadata

org.springframework.boot.autoconfigure.AutoConfigurationMetadata 接口,僅有一個 PropertiesAutoConfigurationMetadata 實現類

這個對象很關鍵,在文中提到不少次數,是解析 META-INF/spring-autoconfigure-metadata.properties 文件生成的對象,里面保存了 Spring Boot 中自動配置類的注解元數據

至於如何解析這個文件,如何生成該對象,這里不在講述,可以回顧上一篇《剖析 @SpringBootApplication 注解》 文章的 AutoConfigureAnnotationProcessor 小節

下面列舉文件中的部分內容

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration=
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.ConditionalOnClass=org.springframework.web.servlet.DispatcherServlet
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.AutoConfigureOrder=-2147483648
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration=
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.ConditionalOnClass=org.springframework.data.redis.core.RedisOperations

看到這個文件是不是明白了,判斷是否要引入 DispatcherServletAutoConfiguration 這個自動配置類,從這個 properties 文件中找到它的 @ConditionalOnClass 注解的值,然后判斷指定的 Class 對象們是否都存在,不存在則表示不需要引入這個自動配置類

當然,還有 @OnBeanCondition@OnWebApplicationCondition 兩個注解的判斷

總結

本文分析了 Spring 的 Condition 接口,配合 @Conditional 注解的使用,可以判斷某個 Class 對象是否有必要生成一個 Bean 被 Spring IoC 容器管理。

由於在 Spring 中 Condition 接口的實現類就一個,Spring Boot 則對 Condition 接口進行擴展,豐富了更多的使用場景,其內部主要用於自動配置類。同時,在 Spring Boot 中提供了很多 Condition 相關的注解,配合 @Conditional 注解一起使用,就能將滿足條件的自動配置類引入進來。例如 @OnClassCondition 注解標注的配置類必須存在所有的指定的 Class 對象們,才將其生成一個 Bean 被 Spring IoC 容器管理。

@EnableAutoConfiguration 注解驅動自動配置模塊的過程中,會通過 AutoConfigurationImportFilter 過濾掉一些不滿足條件的自動配置類,原理和 Condition 差不多,主要通過上面這個 AutoConfigurationMetadata 類,再結合不同的 AutoConfigurationImportFilter 實現類實現的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM