精尽Spring Boot源码分析 - Condition 接口的扩展
该系列文章是笔者在学习 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 接口方法处理过程如下:
从注解元信息中获取所标注的
类名
(或者类名#方法名
)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();
}
调用
getMatchOutcome(..)
方法,获取匹配结果(包含匹配消息),抽象方法,交由子类实现打印匹配日志
protected final void logOutcome(String classOrMethodName, ConditionOutcome outcome) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(getLogMessage(classOrMethodName, outcome));
}
}
向 ConditionEvaluationReport 中记录本次的匹配结果
private void recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome) {
if (context.getBeanFactory() != null) {
ConditionEvaluationReport.get(context.getBeanFactory()).recordConditionEvaluation(classOrMethodName, this, outcome);
}
}
返回匹配结果
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);
}
}
判断是否匹配的过程如下:
获取这个类上面的
@ConditionalOnClass
注解的值,也就是哪些 Class 对象必须存在找到这些 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;
}
如果存在不存在的,那么不符合条件,返回不匹配
添加
@ConditionalOnClass
满足条件的匹配信息
获取这个类上面的
@ConditionalOnMissingClass
注解的值,也就是这些 Class 对象必须都不存在- 找到这些 Class 对象中哪些是存在的,和上面的
1.1
差不多,只不过这里传的是 ClassNameFilter.PRESENT 过滤器 - 如果有一个存在,那么不符合条件,返回不匹配
- 添加
@ConditionalOnMissingClass
满足条件的匹配信息
- 找到这些 Class 对象中哪些是存在的,和上面的
返回符合条件的结果
上面使用到的 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(..)
方法很想,他们的入参不同,不要搞混了,这里的处理过程如下:
- 从 Spring 应用上下文中获取 ConditionEvaluationReport 对象
- 调用
getOutcomes(..)
抽象方法,获取所有自动配置类的匹配结果,空方法,交由子类实现 - 将自动配置类的匹配结果保存至一个
boolean[]
数组中,并将匹配结果一一保存至 ConditionEvaluationReport 中- 注意这里匹配结果为空也表示匹配成功
- 返回所有自动配置类是否满足条件的结果数组
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
一分为二进行处理
- 如果 JVM 可用的处理器不止一个,那么这里用两个线程去处理
- 对
autoConfigurationClasses
所有的自动配置类进行处理,这里是对@ConditionalOnClass
注解进行处理,必须存在指定 Class 类对象
- 对
- 否则,就是单核处理,当前线程去处理
- 创建一个匹配处理器
outcomesResolver
- 返回
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
实现类实现的。
精尽Spring Boot源码分析 - Condition 接口的扩展的更多相关文章
- 精尽Spring Boot源码分析 - 文章导读
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 剖析 @SpringBootApplication 注解
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 支持外部 Tomcat 容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 配置加载
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 日志系统
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - @ConfigurationProperties 注解的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 序言
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
随机推荐
- Linux进阶之find命令、xshell速度慢的解决和Linux警告音的关闭
一.Linux警告音关闭方法 1. 修改/etc/inputrc配置文件 set bell-style none #取消该行注释 2. 修改~/.bashrc配置文件 在后面增加: setter ...
- centos下yum方法安装apache+php+mysql
yum(全称为:Yellow dog Updater,Modified) 是一个在Fedora和RedHat以及SUSE中的Shell前端管理软件.基于RPM包管理,能够从远处镜像服务器下载RPM包并 ...
- 关于Ajax 的 cache 属性 (Day_34)
最近做项目,在某些页面显示,ajax刷新总是拿不到新内容,时常需要清除缓存,才能到达想要的效果. 经过再次查看文档,最后加了一行属性:cache:false 即可解决问题 我们先看下文档的说明: 可以 ...
- IDEA 快速上手指南(全配置)(Day_23)
Idea快速入门指南 1.安装 1.1.安装 我们使用的是2017.3.4版本: 双击打开, 选择一个目录,最好不要中文和空格: 然后选择桌面快捷方式,请选择64位: 然后选择安装: 开始安装: 然后 ...
- Ansible-快速启动
Ansible是一款简单的运维自动化工具,只需要使用ssh协议连接就可以来进行系统管理,自动化执行命令,部署等任务. Ansible的特点 1.ansible轻量级无客户端agentless,只需要双 ...
- 入坑java工程师那些事
最近在知乎上看到好多关于转行做java的朋友,有的在担心学历,有的在想着如何学习java,有的在纠结如何面试.作为一个工作了近10年的java程序员来聊聊我的一些想法,主要从学历.个人能力.工作环境等 ...
- Your branch and 'origin/master' have diverged, and have 1 and 1 different commits each, respectively
On branch master Your branch and 'origin/master' have diverged, and have 1 and 1 different commits e ...
- .Net之简单通知服务
开篇语 这两天看见有大佬分享使用钉钉和企业微信的机器人来做通知报警,然后我想到了我使用的另一个第三方软件捷易快信(可能大家都不知道这个东西,我也忘了我最开始是咋知道的),该服务的优点是可以通过微信进行 ...
- CUDA 9中张量核(Tensor Cores)编程
CUDA 9中张量核(Tensor Cores)编程 Programming Tensor Cores in CUDA 9 一.概述 新的Volta GPU架构的一个重要特点是它的Tensor核,使T ...
- JVM Ecosystem Report 2020 (2020年JVM生态系统报告)
本文翻译自SNYK于2020年发布的< JVM Ecosystem Report 2020 >,全文使用机器翻译自动生成,人为将翻译的离谱和翻译明显错误的地方修正到勉强能看懂的程度. 英语 ...