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 实现类实现的。


__EOF__

本文作者月圆
本文链接https://www.cnblogs.com/lifullmoon/p/14957771.html
关于博主:本着学习与分享的目的,将持续不断的进行知识分享。望各位到访看客如有喜欢的文章,可以点击一下“推荐”,若有不同建议或者意见,也请不吝赐教,博主感激不尽。另外,欢迎转载博主的文章,请务必依据文章下方的版权声明转载。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   月圆吖  阅读(1497)  评论(1编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示