当前位置: 首页 > news >正文

【Spring】Spring Boot过滤不需要的自动配置类过程分析

目录

一、前言

二、提供过滤功能的顶层接口

三、AutoConfigurationImportSelector#filter

3.1 详解

3.2 详解

3.3 详解

3.4 详解

四、FilteringSpringBootCondition#match

4.1 OnClassCondition

4.1.2 详解

4.1.3 StandardOutcomesResolver

4.1.3.1 构造函数

4.1.3.2 StandardOutcomesResolver#resolveOutcomes

4.1.3.2.1 详解

4.1.3.2.2 详解

4.1.4 总结

4.2 OnWebApplicationCondition

4.3 OnBeanCondition


一、前言

在Spring Boot 自动配置原理分析这篇笔记中我们分析了Spring Boot启动并且加载自动配置类的过程,其中加载自动配置类前,进行过滤(如果不提前过滤就需要将类加载到JVM中并解析类信息后才能过滤,因此这里提前过滤是一种性能优化的手段)的方法AutoConfigurationImportSelector#filter,因为相对还比较复杂,因此放在这篇文章中来分析,作为补充。

先来看下调用filter方法时方法参数信息,方便调试,一共两个参数:

  • 一个是List<String> configurations:存储的是META-INF/spring.factories文件中key为EnableAutoConfiguration的自动配置类信息。
  • 另一个是AutoConfigurationMetadata autoConfigurationMetadata:存储的是META-INF/spring-autoconfigure-metadata.properties文件中存储的自动配置类和条件注解信息的组合信息。

META-INF/spring.factories:

META-INF/spring-autoconfigure-metadata.properties:

二、提供过滤功能的顶层接口

执行这个自动配置类过滤过程的顶层接口是org.springframework.boot.autoconfigure.AutoConfigurationImportFilter,源码如下:

@FunctionalInterface
public interface AutoConfigurationImportFilter {boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);
}

从注解@FunctionalInterface可以看出这是一个函数接口,按照函数接口的定义是只有一个抽象方法(注意不是一个方法,还可以有default方法)的接口,这里的方法是match(autoConfigurationClass, authConfigurationMetadata):

  • 第一个参数是需要判定的自动配置类的全限定名称的数组。
  • 第二个参数是待判断的自动配置类上的注解的元信息对象,主要是注解信息,用来判断配置类是否需要引入。

先在org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#filter的debug模式下看下:

match方法最后的返回值是一个布尔类型的数组,这里是和方法入参autoConfigurationClasses数组一一对应的,对应位置是true则代表对应的配置类需要加载,对应位置是false则代表对应的配置类不需要加载。接下来我们再看下AutoConfigurationImportFilter的类图:

从类图中我们可以看到,AutoConfigurationImportFilter的直接子类是FilteringSpringbootCondition,通过这篇文章我们已经知道org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition同时也是SpringBootCondition的子类,而SpringBootCondition是对Spring提供的Condition的扩展,因此FilteringSpringBootCondition就拥有了如下的两个能力:

  1. Spring原生的基于条件注解判断自动配置类是否需要加载,需要则加载,不需要则忽略
  2. 根据条件注解信息,同时判断一批自动配置类是否需要加载的能力(注意这里不会进行加载,加载过程要依赖于能力1)

可以看到一个是单个的判断,一个是批量的判断,可以认为只不过是多了一个for循环而已,因此Spring设计者将这两个功能放在一起,尽量复用可以复用的能力,减少代码量。

三、AutoConfigurationImportSelector#filter

下面就来看实现过滤功能的源码:

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {long startTime = System.nanoTime();// 转换自动配置类的List集合为String数组String[] candidates = StringUtils.toStringArray(configurations);// skip数组用于存储最终的匹配结果,和candidates数组索引位置一一对应,true代表最终需要自动引入,false代表不需要自动引入boolean[] skip = new boolean[candidates.length];boolean skipped = false;// <AutoConfigurationImportSelector#filter_1>见详细讲解,遍历所有的自动配置导入过滤器for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {// <AutoConfigurationImportSelector#filter_2>见详细讲解,回调当前遍历到的AutoConfigurationImportFilter对象的相关AwareinvokeAwareMethods(filter);// <AutoConfigurationImportSelector#filter_3>见详细讲解,这个方法就是真正的过滤逻辑boolean[] match = filter.match(candidates, autoConfigurationMetadata);// 循环当前的自动引入过滤器过滤结果,并记录过滤结果,用于后续逻辑过滤使用for (int i = 0; i < match.length; i++) {if (!match[i]) {skip[i] = true;candidates[i] = null;skipped = true;}}}// 当没有需要过滤的自动配置类时,会进if直接返回,否则执行后续逻辑,通过布尔数组进行过滤if (!skipped) {return configurations;}// 存储过滤后需要自动配置的类List<String> result = new ArrayList<>(candidates.length);for (int i = 0; i < candidates.length; i++) {// 如果当前位置为fasle则说明不需要跳过,则添加到最终结果中if (!skip[i]) {result.add(candidates[i]);}}// <AutoConfigurationImportSelector#filter_4>见详细讲解,打印自动配置导入过滤结果日志。if (logger.isTraceEnabled()) {int numberFiltered = configurations.size() - result.size();logger.trace("Filtered " + numberFiltered + " auto configuration class in "+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");}// 返回结果,不知道为什么要再重新new一个return new ArrayList<>(result);
}

3.1 <AutoConfigurationImportSelector#filter_1>详解

<AutoConfigurationImportSelector#filter_1>处的getAutoConfigurationImportFilters()是获取所有的自动配置导入过滤器,源码如下:

protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {// 从META-INF/spring.factories获取key为org.springframework.boot.autoconfigure.AutoConfigurationImportFilter的所有实现类,如下:/*# Auto Configuration Import Filtersorg.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\org.springframework.boot.autoconfigure.condition.OnBeanCondition,\org.springframework.boot.autoconfigure.condition.OnClassCondition,\org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition*/// 可以看到配置的正是3个实现类return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}

3.2 <AutoConfigurationImportSelector#filter_2>详解

<AutoConfigurationImportSelector#filter_2>处是回调AutoConfigurationImportFilter的相关Aware,源码不是很复杂,如下:

private void invokeAwareMethods(Object instance) {if (instance instanceof Aware) {if (instance instanceof BeanClassLoaderAware) {((BeanClassLoaderAware) instance).setBeanClassLoader(this.beanClassLoader);}if (instance instanceof BeanFactoryAware) {((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);}if (instance instanceof EnvironmentAware) {((EnvironmentAware) instance).setEnvironment(this.environment);}if (instance instanceof ResourceLoaderAware) {((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);}}
}

代码逐个检查实例是否实现了BeanClassLoaderAware、BeanFactoryAware、EnvironmentAware或ResourceLoaderAware接口之一。如果实现了这些接口中的任意一个,相应的方法将被调用来设置ClassLoader、BeanFactory、环境(Environment)或资源加载器(ResourceLoader)。这些操作允许bean在创建时获取框架运行时的重要组件和配置。这几个接口的作用如下:

  • BeanClassLoaderAware: 允许bean获取到加载自己的ClassLoader。类加载器对于加载资源和类非常关键。
  • BeanFactoryAware: 允许bean引用BeanFactory,一个管理bean生命周期和依赖的工厂。
  • EnvironmentAware: 允许bean访问Spring环境抽象,这可以用于读取配置和解析属性。
  • ResourceLoaderAware: 允许bean获取到ResourceLoader,可以用来加载资源文件,如配置文件。

3.3 <AutoConfigurationImportSelector#filter_3>详解

<AutoConfigurationImportSelector#filter_3>处后续单独讲解。这个方法就是真正的过滤逻辑。

讲解见:四、FilteringSpringBootCondition#match

3.4 <AutoConfigurationImportSelector#filter_4>详解

<AutoConfigurationImportSelector#filter_4>处是打印自动配置导入过滤结果日志,修改application.properties文件增加logging.level.org.springframework=trace然后debug查看:

四、FilteringSpringBootCondition#match

在第三章节:AutoConfigurationImportSelector#filter部分的讲解中,由FilteringSpringBootCondition#match方法完成过滤不需要加载的自动配置类的工作,这个功能是通过OnClassConditionOnWebApplicationConditionOnBeanCondition三个条件类完成,调用的方法都是match,我们先来验证下。

第1个条件类OnClassCondition:

第2个条件类OnClassCondition:

第3个条件类OnBeanCondition:

Spring Boot 提供的条件注解

Spring Boot中的常用条件注解有:

  1. ConditionalOnBean:是否存在某个某类或某个名字的Bean
  2. ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean
  3. ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个
  4. ConditionalOnClass:是否存在某个类
  5. ConditionalOnMissingClass:是否缺失某个类
  6. ConditionalOnExpression:指定的表达式返回的是true还是false
  7. ConditionalOnJava:判断Java版本
  8. ConditionalOnWebApplication:当前应用是不是一个Web应用
  9. ConditionalOnNotWebApplication:当前应用不是一个Web应用
  10. ConditionalOnProperty:Environment中是否存在某个属性

接着我们开始分析本部分的内容,即FilteringSpringbootCondition的骨架方法org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#match,源码如下:

org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#match
/*** 判断自动配置类是否匹配条件,并返回匹配结果集* @param autoConfigurationClasses 自动配置类全限定名字符串数组* @param autoConfigurationMetadata 自动配置元数据对象* @return 匹配结果集*/
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {// 创建负责记录某些信息的类,具体暂时不用深究,用到了再看ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);// <FilteringSpringBootCondition#match_1>ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);// 创建存储最终匹配结果集的数组boolean[] match = new boolean[outcomes.length];// 循环ConditionOutcome数组设置最终匹配的结果集for (int i = 0; i < outcomes.length; i++) {match[i] = (outcomes[i] == null || outcomes[i].isMatch());// <FilteringSpringBootCondition#match_2>// 如果是不匹配并且有ConditionOutCome信息则日志打印,// 方便定位过滤了哪些自动配置类if (!match[i] && outcomes[i] != null) {logOutcome(autoConfigurationClasses[i], outcomes[i]);if (report != null) {report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);}}}return match;
}

<FilteringSpringBootCondition#match_1>处与三个子类相关,因此需要在具体的子类中来分析该方法。

<FilteringSpringBootCondition#match_2>,修改application.properties文件增加logging.level.org.springframework=trace然后debug查看:

下面我们开始通过具体的子类OnClassCondition来开始吧!

4.1 OnClassCondition

这个的条件是项目必须存在指定的类,才算条件成立。符合该条件的自动配置类才会被加载。

关于@ConditionalOnClass注解的讲解见:二、@ConditionalOnClass的底层工作原理

为了方便我们调试,先增加如下的条件变量:

位置org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#match

 源码如下:

org.springframework.boot.autoconfigure.condition.OnClassCondition#getOutcomes
/*** 获取自动配置类的条件结果集,这里使用了多线程处理自动配置类过滤,* 具体的处理过程可以查看ThreadedOutcomesResolver的resolveOutcomes方法。* @param autoConfigurationClasses 自动配置类全限定名字符串数组* @param autoConfigurationMetadata 自动配置元数据对象,它里面会存储着每一个自动配置类条件注解信息,可以用来判断条件注解是否成立* @return 条件结果集数组*/
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,AutoConfigurationMetadata autoConfigurationMetadata) {// 用于将待过滤的自动配置类一分为二,具体是使用new Thread()创建一个线程来处理前一半,另一半直接使用当前的主线程处理// 这样做是为了提高自动配置类过滤的效率,因为自动配置类比较多// 在2.1.14.RELEASE版本中有117个,在2.2.20版本中已经达到了124个// 随着版本的不断迭代自动配置类数量会越来越多int split = autoConfigurationClasses.length / 2;// <OnClassCondition#getOutcomes_1>// 创建线程执行过滤任务,后续分小节详细分析,这行代码执行完,创建的线程就开始执行过滤工作了// 内部创建OutcomesResolver的子类ThreadedOutcomesResolver,用于过滤数组的前半部分OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,autoConfigurationMetadata);// 直接创建StandardOutcomesResolver对象用于执行过滤操作,用于过滤数组的后半部分OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());// 解析输出结果数组,这里调用的是StandardOutcomesResolver的方法ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();// <OnClassCondition#getOutcomes_2>// 解析输出结果数组,这里调用的是ThreadedOutcomesResolver的方法ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();// 创建结果数组,并使用System.arraycopy方法拷贝firstHalf和secondHalf到结果集中ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);// 返回结果集return outcomes;
}

<OnClassCondition#getOutcomes_2>处源码如下:

org.springframework.boot.autoconfigure.condition.OnClassCondition.ThreadedOutcomesResolver#resolveOutcomes
public ConditionOutcome[] resolveOutcomes() {try {// 这里强制让主线程即当前调用方法的线程等待this.thread执行完毕,// 保证创建的线程完成前一半自动配置类的工作,// 从而最终设置this.outcomesthis.thread.join();}catch (InterruptedException ex) {Thread.currentThread().interrupt();}// 返回最终的结果return this.outcomes;
}

4.1.1 OutcomesResolver接口

前面源码中firstHalfResolver和secondHalfResolver都是OutcomesResolver接口的实现类实例,resolveOutcomes()也是OutcomesResolver接口提供的方法,该接口是一个内部接口,源码如下:

org.springframework.boot.autoconfigure.condition.OnClassCondition.OutcomesResolver
private interface OutcomesResolver {ConditionOutcome[] resolveOutcomes();
}

该接口有两个实现类,如下图:

最终的过滤工作就是依赖于这两个类来完成的。

4.1.2 <OnClassCondition#getOutcomes_1>详解

回顾下代码位置:

org.springframework.boot.autoconfigure.condition.OnClassCondition#getOutcomes
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,AutoConfigurationMetadata autoConfigurationMetadata) {....OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,autoConfigurationMetadata);....
}

createOutcomesResolver源码如下:

org.springframework.boot.autoconfigure.condition.OnClassCondition#createOutcomesResolver
private OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses, int start, int end,AutoConfigurationMetadata autoConfigurationMetadata) {// 创建StandardOutcomesResolverOutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, start, end,autoConfigurationMetadata, getBeanClassLoader());try {// <OnClassCondition#createOutcomesResolver_1>// 创建ThreadedOutcomesResolver,并指定outcomesResolver参数(就是前面创建的StandardOutcomesResolver)// 说明内部的自动配置类的过滤工作还是通过StandardOutcomesResolver完成的return new ThreadedOutcomesResolver(outcomesResolver);}catch (AccessControlException ex) {return outcomesResolver;}
}

<OnClassCondition#createOutcomesResolver_1>处源码如下:

private ThreadedOutcomesResolver(OutcomesResolver outcomesResolver) {// 创建线程,并调用outcomesResolver(StandardOutcomesResolver)的resolveOutcomes方法完成自动配置类的过滤this.thread = new Thread(() -> this.outcomes = outcomesResolver.resolveOutcomes());// 启动线程this.thread.start();
}

从以上可以看到,最终自动配置类的过滤工作都是通过类org.springframework.boot.autoconfigure.condition.OnClassCondition.StandardOutcomesResolver来完成的,那么我们接下来就重点分析这个类。

4.1.3 StandardOutcomesResolver

4.1.3.1 构造函数

源码:

org.springframework.boot.autoconfigure.condition.OnClassCondition.StandardOutcomesResolver#StandardOutcomesResolver
private StandardOutcomesResolver(String[] autoConfigurationClasses, int start, int end,AutoConfigurationMetadata autoConfigurationMetadata, ClassLoader beanClassLoader) {// 需要过滤的自动配置类class名称集合数组this.autoConfigurationClasses = autoConfigurationClasses;// 要过滤this.autoConfigurationClasses的开始位置下标this.start = start;// 要过滤this.autoConfigurationClasses的结束位置下标this.end = end;// <StandardOutcomesResolver#StandardOutcomesResolver_1>// 自动配置类和条件注解组合的信息类,即每个配置类的每个条件注解中设置的信息this.autoConfigurationMetadata = autoConfigurationMetadata;// 设置类加载器this.beanClassLoader = beanClassLoader;
}

<StandardOutcomesResolver#StandardOutcomesResolver_1>处autoConfigurationMetadata存储的是每个自动配置类和条件注解组合对应的值信息,我们以WebMvcAutoConfiguration自动配置类为例,先看下WebMvcAutoConfiguration的源码:

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}

因为我们这里分析的是OnClassCondition的条件类,那么我们就以ConditionalOnClass条件注解类为例,来看下存储的信息,如下图:

可以看到其格式是key:条件类全限定名+注解名,value:注解设置的值信息。

从前面的分析可以看到不管是使用ThreadedOutcomesResolver还是StandardOutcomesResolver都是使用的后者的resolveOutcomes方法来过滤自动配置类的,因此,我们只要分析清楚这个点流程基本上也就通了,下面开始分析。

4.1.3.2 StandardOutcomesResolver#resolveOutcomes

源码如下:

org.springframework.boot.autoconfigure.condition.OnClassCondition.StandardOutcomesResolver#resolveOutcomes
public ConditionOutcome[] resolveOutcomes() {return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
}

继续:

org.springframework.boot.autoconfigure.condition.OnClassCondition.StandardOutcomesResolver#getOutcomes
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,AutoConfigurationMetadata autoConfigurationMetadata) {// 根据end和start的差值大小创建ConditionOutcome结果数组ConditionOutcome[] outcomes = new ConditionOutcome[end - start];// 依次处理每个待过滤的自动配置类for (int i = start; i < end; i++) {// 获取当前自动配置类的全限定名,如// org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationString autoConfigurationClass = autoConfigurationClasses[i];// 不为空则继续处理if (autoConfigurationClass != null) {// <StandardOutcomesResolver#getOutcomes_1>,获取在ConditionalOnClass注解上配置的要求存在的类,需要存在这些类条件注解才成立String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");if (candidates != null) {// <StandardOutcomesResolver#getOutcomes_2>// 根据候选的待检测class类名称获取ConditionOutcome// 并放到结果数组的对应位置outcomes[i - start] = getOutcome(candidates);}}}// 返回ConditionOutcome结果数组return outcomes;
}

4.1.3.2.1 <StandardOutcomesResolver#getOutcomes_1>详解

<StandardOutcomesResolver#getOutcomes_1>获取在ConditionalOnClass注解上配置的要求存在的类,如org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration配置如下(省略其它注解):

@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
public class WebMvcAutoConfiguration {}

则结果就是Servlet.class、DispatcherServlet.class、WebMvcConfigurer.class这三个class对应的类的全限定名,下面开始看源码:

org.springframework.boot.autoconfigure.AutoConfigurationMetadataLoader.PropertiesAutoConfigurationMetadata#get(java.lang.String, java.lang.String)
public String get(String className, String key) {return get(className, key, null);
}

假定当前的className是org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,key值就是传入的ConditionalOnClass,debug如下:

继续看get方法:

org.springframework.boot.autoconfigure.AutoConfigurationMetadataLoader.PropertiesAutoConfigurationMetadata#get(java.lang.String, java.lang.String, java.lang.String)
public String get(String className, String key, String defaultValue) {String value = this.properties.getProperty(className + "." + key);return (value != null) ? value : defaultValue;
}

通过这个方法就获取到了@ConditionalOnClass注解中设置的value值。

 

还是以WebMvcAutoConfiguration为例进行debug,如下:

那么<StandardOutcomesResolver#getOutcomes_1>处的结果就是javax.servlet.Servlet,org.springframework.web.servlet.config.annotation.WebMvcConfigurer,org.springframework.web.servlet.DispatcherServlet了,这个结果会被用作<StandardOutcomesResolver#getOutcomes_2>的参数

4.1.3.2.2 <StandardOutcomesResolver#getOutcomes_2>详解

getOutcome()方法源码如下:

org.springframework.boot.autoconfigure.condition.OnClassCondition.StandardOutcomesResolver#getOutcome(java.lang.String)
private ConditionOutcome getOutcome(String candidates) {try {// 待判断是否存在条件类只有一个if (!candidates.contains(",")) {return getOutcome(candidates, this.beanClassLoader);}// 包含逗号,说明待判断是否存在条件类有多个for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {// <StandardOutcomesResolver#getOutcome_1>// 获取过滤匹配结果,这里就是最终执行过滤逻辑的地方。candidate参数是条件注解上设置的值,必须要存在这些类条件注解才能成立。ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader);// 这里一旦不为null则说明不匹配,就可以直接返回了if (outcome != null) {return outcome;}}}catch (Exception ex) {// We'll get another chance later}return null;
}

<StandardOutcomesResolver#getOutcome_1>处代码是执行最终的匹配工作,源码如下:

org.springframework.boot.autoconfigure.condition.OnClassCondition.StandardOutcomesResolver#getOutcome(className, classLoader)
private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {// <StandardOutcomesResolver#getOutcome(className, classLoader)_1>if (ClassNameFilter.MISSING.matches(className, classLoader)) {// 如果是不匹配,则直接返回携带有相关不匹配信息的ConditionOutcome对象return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class).didNotFind("required class").items(Style.QUOTE, className));}// 如果是到这里,则说明是匹配的,直接返回nullreturn null;
}

<StandardOutcomesResolver#getOutcome(className, classLoader)_1>处代码是使用ClassNameFilter.MISSING类的matches(className, classLoader)方法来判断指定的类是不是MISSING,如果是则返回true,否则返回false,源码如下:

org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition.ClassNameFilter
protected enum ClassNameFilter {MISSING {@Overridepublic boolean matches(String className, ClassLoader classLoader) {return !isPresent(className, classLoader);}};public abstract boolean matches(String className, ClassLoader classLoader);
}

可以看到其实是个枚举,个人觉得这样实现的代码还是挺优雅的,然后看下其中的isPresent(className, classLoader)方法,源码如下:

org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition.ClassNameFilter#isPresent
// 入参className是条件注解中设置的类的全限定名
public static boolean isPresent(String className, ClassLoader classLoader) {// 如果是传入的类加载器为null则获取默认的类加载器if (classLoader == null) {classLoader = ClassUtils.getDefaultClassLoader();}try {// <ClassNameFilter#isPresent_1>forName(className, classLoader);return true;}catch (Throwable ex) {return false;}
}

<ClassNameFilter#isPresent_1>处源码:

org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition.ClassNameFilter#forName
private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException {// 如果有指定的类加载器,则用指定的类加载器去尝试加载条件注解中设置的类的全限定名if(classLoader != null) {return classLoader.loadClass(className);}// 如果没有指定类加载器,则直接使用当前的类加载器去尝试加载条件注解中设置的类的全限定名,注意forName()方法会委托给当前线程的上下文类加载器去尝试加载return Class.forName(className);
}

可以看到就是使用类加载器加载了,能加载到全限定名为className的类则返回true,加载不到则返回false。也就是说只要是发现类加载器加载到了条件注解中设置的类,就说明条件注解成立。

4.1.4 总结

整个过程就是先获取到所有的自动配置类列表,然后获取到每一个自动配置类上条件注解的数据,得到每一个配置类的条件注解的条件。

然后就遍历每一个自动配置类,然后通过类加载机制去尝试加载它条件注解中设置的类,如果能加载到条件注解中设置的类,说明当前项目存在这个类,条件注解的条件成立,那么这个自动配置类就可以被解析加载,反之这个自动配置类就要被过滤掉不去加载。

最终就会得到一个布尔型结果数组,每一个位置对应一个自动配置类,然后每一个位置的值就是true或false,表示对应的自动配置类要不要进行加载。

4.2 OnWebApplicationCondition

这个的条件是当前项目必须是指定类型的Web项目,才算条件成立。符合该条件的自动配置类才会被加载。例如:@ConditionalOnWebApplication(type = Type.SERVLET):表示项目应用类型得是Spring MVC才算符合条件,Type.SERVLET表示的就是Spring MVC类型的Web项目。

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#filter设置如下条件变量,方便调试:

首先是执行FilterringSpringbootCondition的模板方法org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#match如下:

public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {// 条件执行报告器ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);// <1>// 调用具体类的方法实现,获取条件输出结果// 这里调用的是OnWebApplicationCondition的实现类ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);// 定义自动配置类是否过滤的结果数组boolean[] match = new boolean[outcomes.length];// 循环条件输出结果,如果是条件输出为null或者是match属性为true则认为是匹配的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]);}}}// 返回匹配结果return match;
}

其中<1>处代码是调用的org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition#getOutcomes源码如下:

protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,AutoConfigurationMetadata autoConfigurationMetadata) {// 创建条件输出的结果数组ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];// 循环条件输出结果处理每个自动配置的过滤类for (int i = 0; i < outcomes.length; i++) {// 通过索引位置获取当前的自动配置了,并进行判断String autoConfigurationClass = autoConfigurationClasses[i];// 如果自动配置类不为null,则进行判断if (autoConfigurationClass != null) {// <OnWebApplicationCondition#getOutcomes_1>// 调用getOutcome方法完成判断,并赋值到结果输出数组的// 对应索引位置outcomes[i] = getOutcome(autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"));}}// 返回条件输出结果数组return outcomes;
}

<OnWebApplicationCondition#getOutcomes_1>处代码我们还是以WebMvcAutoConfiguration为例,增加如下的条件变量:

方法autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication")结果如下图:

则方法getOutcome的参数就是SERVLET接着来看getOutcome方法:

private ConditionOutcome getOutcome(String type) {// 如果是type为null,则直接返回null代表结果为匹配if (type == null) {return null;}// 使用构造器构造消息ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class);// 处理ConditionalOnWebApplication.Type.SERVLET类型的web应用程序,可以认为就是springmvc上下文环境// 传入到isPresent方法的参数SERVLET_WEB_APPLICATION_CLASS就是该类型Web项目需要依赖的类全限定名if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) {if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())) {return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());}}// 处理ConditionalOnWebApplication.Type.REACTIVE,暂时没用过// 传入到isPresent方法的参数REACTIVE_WEB_APPLICATION_CLASS就是该类型Web项目需要依赖的类全限定名if (ConditionalOnWebApplication.Type.REACTIVE.name().equals(type)) {if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {return ConditionOutcome.noMatch(message.didNotFind("reactive web application classes").atAll());}}// 如果执行到这里则说明是任意的web类型org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type#ANY,其中// 则判断servlet或者reactive二者存在其一即可SERVLET_WEB_APPLICATION_CLASS// REACTIVE_WEB_APPLICATION_CLASS是二者用于判断的标记类if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())&& !ClassUtils.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {return ConditionOutcome.noMatch(message.didNotFind("reactive or servlet web application classes").atAll());}// 返回null,认为匹配return null;
}

注意到在上面的源码中都使用了org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition.ClassNameFilter#isPresent方法,该方法源码如下:

// 传入的className就是要判断的类名,classLoader是类加载器,如果没有传入,则使用默认的类加载器。
// 当前是要判断项目是否为Web项目,所以这个className就是对应类型的Web项目需要,通过类加载机制判断该项目是否存在该类
public static boolean isPresent(String className, ClassLoader classLoader) {if (classLoader == null) {classLoader = ClassUtils.getDefaultClassLoader();}try {forName(className, classLoader);return true;}catch (Throwable ex) {return false;}
}

forName(className, classLoader);:

// 使用类加载器尝试加载className类,看是否能够加载到
private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException {if (classLoader != null) {return classLoader.loadClass(className);}return Class.forName(className);
}

其中的方法classLoader.loadClass(className);当加载的类在classpath下无法加载到时将会抛出java.lang.ClassNotFoundException该方法抛出该异常会被调用者通过catch (Throwable ex) {return false;}捕获,直接返回false,代表期望存在的类没有present,如下代码测试:

public static void main(String[] args) throws Exception {ClassLoader classLoader = Foo.class.getClassLoader();String exitsClassFullName = "java.util.ArrayList";String notExistClassFullName = "not.exits.class.name";Class<?> exitsClass = classLoader.loadClass(exitsClassFullName);System.out.println("exitsClass is: ");System.out.println(exitsClass);Class<?> notExistClass = classLoader.loadClass(notExistClassFullName);System.out.println("notExistClass is: ");System.out.println(notExistClass);
}

运行如下:

exitsClass is: 
class java.util.ArrayList
notExistClass is: 
Exception in thread "main" java.lang.ClassNotFoundException: not.exits.class.nameat java.net.URLClassLoader.findClass(URLClassLoader.java:381)at java.lang.ClassLoader.loadClass(ClassLoader.java:424)at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)at java.lang.ClassLoader.loadClass(ClassLoader.java:357)at Foo.main(Foo.java:14)
Process finished with exit code 1

4.3 OnBeanCondition

该条件类和ConditionalOnBeanConditionalOnMIssingBean,ConditionalOnSingleCandidate等注解配合使用,当容器里有指定 Bean 时条件成立。符合条件的自动配置类会被解析加载。

关于这个类的详细讲解见:三、@ConditionalOnBean的底层工作原理


相关文章:【Spring】Spring Boot 自动配置原理分析_springboot自动配置原理答案-CSDN博客
                  【Spring】Spring Boot 自动配置_springboot自动配置-CSDN博客
                  【Spring】Spring Boot启动过程源码解析_springboot启动源码解析-CSDN博客
                  【Spring】Java SPI机制及Spring Boot使用实例_spring spi-CSDN博客

http://www.dtcms.com/a/550191.html

相关文章:

  • 可做产品预售的网站怎么自己做彩票网站吗
  • 营销型网站维护费用网页链接提取码怎么用
  • SQL优化实战:从慢查询到高效查询
  • 厦门网站建设 金猪凡客登录入口
  • 兴仁县城乡建设局网站汕头市城市建设开发总公司
  • 商城网站验收标准可以看那种东西的手机浏览器
  • 驻马店手机网站制作网站开发手册
  • 03-BUG的定义和生命周期+软件测试BUG管理流程
  • 网站快照查询企业宣传网站建设需求说明书样文
  • Rust入门开发之Rust 循环语法详解
  • Statsig面试全攻略:电话面+四轮VO真题分享
  • The 2025 ICPC Asia East Continent Online Contest (I) - H.Walk(网格图对偶建模、最小割建模)
  • 网站的后缀名怎么建设おっさんとわたし天堂
  • 平台网站建设后台源码怎么做p2p网站
  • 网站设计风格方案昌乐网站设计
  • Java 与 C 差异
  • OAuth 2.0 安全授权
  • Rust 与数据库连接池的集成:安全与性能的深度耦合
  • 台州网站策划台州网站策划首页制作教程
  • 中国站长站甘肃省住房和建设厅官方网站
  • Golang学习笔记:后端性能优化秘籍(持续更新)
  • Easyx图形库应用(基础的AI开发原理)
  • arthas实现类的热部署
  • Rust 注释与文档注释:从代码可读性到 API 文档化的工程实践
  • 取名网站怎么做2022年新闻摘抄十条简短
  • 网站开发工具教程wordpress 关键词获取
  • tensorflow的广播机制
  • MIT-最大连续子序列和(MCS)
  • 深圳市住建局网站官网济南网站建设公司哪家好
  • Kubernetes资源管理全解析