springboot过滤不需要的自动配置类过程分析

springboot 专栏收录该内容
27 篇文章 0 订阅

写在前面

这篇文章我们分析了springboot启动并且加载自动配置类的过程,其中加载自动配置类前,进行过滤的方法org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#filter,因为相对还比较复杂,因此放在这篇文章中来分析,作为补充。
先来看下调用filter方法时方法参数信息,方便调试,一共两个参数,一个是List<String> configurations,存储的是META-INF/spring.factories文件中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的自动配置类信息,另一个是AutoConfigurationMetadata autoConfigurationMetadata存储的是META-INF/spring-autoconfigure-metadata.properties文件中存储的自动配置类和条件注解信息的组合信息,分别如下:
在这里插入图片描述
在这里插入图片描述

1:接口

执行这个自动配置类过滤过程的顶层接口是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#filterdebug看下:
在这里插入图片描述
在这里插入图片描述
最后的返回值是一个布尔类型的数组,这里是和autoConfigurationClasses一一对应的,对应位置是true则代表对应的配置类需要加载,对应位置是false则代表对应的配置类不需要加载。接下来我们再看下AutoConfigurationImportFilter的类图:
在这里插入图片描述
从类图中我们可以看到,AutoConfigurationImportFilter的直接子类是FilteringSpringbootCondition,通过这篇文章我们已经知道org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition同时也是SpringBootCondition的子类,而SpringBootCondition是对spring提供的Condition的扩展,因此FilteringSpringBootCondition就拥有了如下的两个能力:

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

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

2:AutoConfigurationImportSelector#filter

源码:

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
	long startTime = System.nanoTime();
	// 转换自动配置类的List集合为String数组
	String[] candidates = StringUtils.toStringArray(configurations);
	// 存储最终的匹配结果,和candidates数组索引位置一一对应,true代表最终需要
	// 自动引入,false代表不需要自动引入
	boolean[] skip = new boolean[candidates.length];
	boolean skipped = false;
	// <AutoConfigurationImportSelector#filter_1>见详细讲解
	for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
		// <AutoConfigurationImportSelector#filter_2>见详细讲解
		invokeAwareMethods(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);
}

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

protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
	// 从META-INF/spring.factories获取key为org.springframework.boot.autoconfigure.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
	*/
	// 可以看到配置的正是3个实现类
	return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}

<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);
		}
	}
}

<AutoConfigurationImportSelector#filter_3>处后续单起讲解。<AutoConfigurationImportSelector#filter_4>处是打印自动配置导入过滤结果日志,修改application.properties文件增加logging.level.org.springframework=trace然后debug查看:
在这里插入图片描述

3:FilteringSpringBootCondition#match

2:AutoConfigurationImportSelector#filter部分的讲解中,过滤不需要加载的自动配置类的工作是通过OnClassConditionOnWebApplicationCondition,OnBeanCondition三个条件类完成,调用的方法都是match,我们先来验证下。
第1个条件类:
在这里插入图片描述
第2个条件类:
在这里插入图片描述
第3个条件类:
在这里插入图片描述
接着我们开始分析本部分的内容,即FilteringSpringbootCondition的骨架方法org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#match,源码如下:

org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#match
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:OnClassCondition

为了方便我们调试,先增加如下的条件变量:
位置org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#match
在这里插入图片描述
源码如下:

org.springframework.boot.autoconfigure.condition.OnClassCondition#getOutcomes
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.outcomes
		this.thread.join();
	}
	catch (InterruptedException ex) {
		Thread.currentThread().interrupt();
	}
	// 返回最终的结果
	return this.outcomes;
}

4.1:OutcomesResolver接口

该接口是一个内部接口,源码如下:

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

其有两个实现类,如下图:
在这里插入图片描述
最终的过滤工作就是依赖于这两个类来完成的。

4.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) {
	// 创建StandardOutcomesResolver
	OutcomesResolver outcomesResolver = new StandardOutcomesResolver(autoConfigurationClasses, start, end,
			autoConfigurationMetadata, getBeanClassLoader());
	try {
		// <OnClassCondition#createOutcomesResolver_1>
		// 创建ThreadedOutcomesResolver,并指定outcomesResolver参数
		// 说明内部的自动配置类的过滤工作还是通过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.3:StandardOutcomesResolver

4.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.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.WebMvcAutoConfiguration
		String autoConfigurationClass = autoConfigurationClasses[i];
		// 不为空则继续处理
		if (autoConfigurationClass != null) {
			// <StandardOutcomesResolver#getOutcomes_1>
			String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
			if (candidates != null) {
				// <StandardOutcomesResolver#getOutcomes_2>
				// 根据候选的待检测class类名称获取ConditionOutcome
				// 并放到结果数组的对应位置
				outcomes[i - start] = getOutcome(candidates);
			}
		}
	}
	// 返回ConditionOutcome结果数组
	return outcomes;
}

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

还是以WebMvcAutoConfiguration为例,如下:
在这里插入图片描述
那么<StandardOutcomesResolver#getOutcomes_1>处的结果就是javax.servlet.Servlet,org.springframework.web.servlet.config.annotation.WebMvcConfigurer,org.springframework.web.servlet.DispatcherServlet了,这个结果会被用作<StandardOutcomesResolver#getOutcomes_2>的参数,源码如下:

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>
			// 获取过滤匹配结果
			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));
	}
	// 如果是到这里,则说明是匹配的,直接返回null
	return 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 {
		@Override
		public 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
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);
	}
	return Class.forName(className);
}

可以看到就是使用类加载器加载了,加载到则返回true,加载不到则返回false。

5:OnWebApplicationCondition

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上下文环境
	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,暂时没用过
	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方法,该方法源码如下:

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);:

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.name
	at 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

6:OnBeanCondition

该条件类和ConditionalOnBeanConditionalOnMIssingBean,ConditionalOnSingleCandidate等注解配合使用,我暂时还没有研究,等研究了再补充。

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
<p style="font-size:16px;color:#666666;"> <img src="https://img-bss.csdn.net/202001311426171105.png" alt="" /> </p> <p style="font-size:16px;color:#666666;"> <strong><span style="font-size:20px;">课程目标</span></strong> </p> <p style="font-size:16px;color:#666666;"> 《从零开始学Scrapy网络爬虫》从零开始,循序渐进地介绍了目前流行的网络爬虫框架Scrapy。即使你没有任何编程基础,学习起来也不会有压力,因为我们有针对性地介绍了Python编程技术。另外,《从零开始学Scrapy网络爬虫》在讲解过程中以案例为导向,通过对案例的不断迭代、优化,让读者加深对知识的理解,并通过14个项目案例,提高学习者解决实际问题的能力。 </p> <p style="font-size:16px;color:#666666;"> <br /> </p> <p style="font-size:16px;color:#666666;"> <strong><span style="font-size:20px;">适合对象</span></strong> </p> <p style="font-size:16px;color:#666666;"> 爬虫初学者、爬虫爱好者、高校相关专业的学生、数据爬虫工程师。 </p> <p style="font-size:16px;color:#666666;"> <br /> </p> <p style="font-size:16px;color:#666666;"> <span style="font-size:20px;"><strong>课程介绍</strong></span> </p> <p style="font-size:16px;color:#666666;"> 《从零开始学Scrapy网络爬虫》共13章。其中,第1~4章为基础篇,介绍了Python基础、网络爬虫基础、Scrapy框架及基本的爬虫功能。第5~10章为进阶篇,介绍了如何将爬虫数据存储于MySQL、MongoDB和Redis数据库中;如何实现异步AJAX数据的爬取;如何使用Selenium和Splash实现动态网站的爬取;如何实现模拟登录功能;如何突破反爬虫技术,以及如何实现文件和图片的下载。第11~13章为高级篇,介绍了使用Scrapy-Redis实现分布式爬虫;使用Scrapyd和Docker部署分布式爬虫;使用Gerapy管理分布式爬虫,并实现了一个抢票软件的综合项目。 </p> <p style="font-size:16px;color:#666666;"> <span style="color:#FF0000;">      由于目标网站可能会对页面进行改版或者升级反爬虫措施,如果发现视频中的方法无法成功爬取数据,敬请按照页面实际情况修改XPath的路径表达式。视频教程主要提供理论、方法支撑。我们也会在第一时间更新源代码,谢谢!</span> </p> <p style="font-size:16px;color:#666666;"> <img src="https://img-bss.csdn.net/202001311426306665.png" alt="" /> </p> <p style="font-size:16px;color:#666666;"> <strong><span style="font-size:20px;">课程特色</span></strong> </p> <p style="font-size:16px;"> <img src="https://img-bss.csdn.net/202001311426415123.png" alt="" /> </p> <div> <br /> </div>
<div style="color:rgba(0,0,0,.75);"> <span style="color:#4d4d4d;"> </span> <div style="color:rgba(0,0,0,.75);"> <span style="color:#4d4d4d;"> </span> <div style="color:rgba(0,0,0,.75);"> <div style="color:rgba(0,0,0,.75);"> <span style="color:#4d4d4d;">当前课程中商城项目的实战源码是我发布在 GitHub 上的开源项目 newbee-mall (新蜂商城),目前已有 6300 多个 star,</span><span style="color:#4d4d4d;">本课程是一个 Spring Boot 技术栈的实战课程,课程共分为 3 大部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 商城项目功能的讲解,让大家实际操作并实践上手一个大型的线上商城项目,并学习到一定的开发经验以及其中的开发技巧。<br /> 商城项目所涉及的功能结构图整理如下:<br /> </span> </div> <div style="color:rgba(0,0,0,.75);">   </div> <div style="color:rgba(0,0,0,.75);"> <p style="color:#4d4d4d;"> <img alt="modules" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3N0b3JlL25ld2JlZS1tYWxsLXMucG5n?x-oss-process=image/format,png" /> </p> </div> <p style="color:rgba(0,0,0,.75);"> <strong><span style="color:#e53333;">课程特色</span></strong> </p> <p style="color:rgba(0,0,0,.75);">   </p> <div style="color:rgba(0,0,0,.75);">   </div> <div style="color:rgba(0,0,0,.75);"> <ul> <li> 对新手开发者十分友好,无需复杂的操作步骤,仅需 2 秒就可以启动这个完整的商城项目 </li> <li> 最终的实战项目是一个企业级别的 Spring Boot 大型项目,对于各个阶段的 Java 开发者都是极佳的选择 </li> <li> 实践项目页面美观且实用,交互效果完美 </li> <li> 教程详细开发教程详细完整、文档资源齐全 </li> <li> 代码+讲解+演示网站全方位保证,向 Hello World 教程说拜拜 </li> <li> 技术栈新颖且知识点丰富,学习后可以提升大家对于知识的理解和掌握,可以进一步提升你的市场竞争力 </li> </ul> </div> <p style="color:rgba(0,0,0,.75);">   </p> <p style="color:rgba(0,0,0,.75);"> <span style="color:#e53333;">课程预览</span> </p> <p style="color:rgba(0,0,0,.75);">   </p> <div style="color:rgba(0,0,0,.75);">   </div> <div style="color:rgba(0,0,0,.75);"> <p style="color:#4d4d4d;"> 以下为商城项目的页面和功能展示,分别为: </p> </div> <div style="color:rgba(0,0,0,.75);"> <ul> <li> 商城首页 1<br /> <img alt="" src="https://img-bss.csdnimg.cn/202103050347585499.gif" /> </li> <li> 商城首页 2<br /> <img alt="" src="https://img-bss.csdn.net/202005181054413605.png" /> </li> <li>   </li> <li> 购物车<br /> <img alt="cart" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3QvY2FydC5wbmc?x-oss-process=image/format,png" /> </li> <li> 订单结算<br /> <img alt="settle" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3Qvc2V0dGxlLnBuZw?x-oss-process=image/format,png" /> </li> <li> 订单列表<br /> <img alt="orders" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3Qvb3JkZXJzLnBuZw?x-oss-process=image/format,png" /> </li> <li> 支付页面<br /> <img alt="" src="https://img-bss.csdn.net/201909280301493716.jpg" /> </li> <li> 后台管理系统登录页<br /> <img alt="login" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3QvbWFuYWdlLWxvZ2luLnBuZw?x-oss-process=image/format,png" /> </li> <li> 商品管理<br /> <img alt="goods" src="https://imgconvert.csdnimg.cn/aHR0cHM6Ly9uZXdiZWUtbWFsbC5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20vcG9zdGVyL3Byb2R1Y3QvbWFuYWdlLWdvb2RzLnBuZw?x-oss-process=image/format,png" /> </li> <li> 商品编辑<br /> <img alt="" src="https://img-bss.csdnimg.cn/202103050348242799.png" /> </li> </ul> </div> </div> </div> </div>
<p style="color:#333333;"> <strong> </strong> </p> <p style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <strong><span style="color:#337FE5;">[为什么要学习Spring Cloud微服务]</span> </strong> </p> <p style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <strong><span style="color:#4D555D;"></span> </strong> </p> <p class="ql-long-24357476" style="font-family:"color:#222226;font-size:14px;background-color:#FFFFFF;"> <strong><span style="font-family:"background-color:#FFFFFF;">SpringCloud作为主流微服务框架,<span style="color:#4D555D;">已成为各互联网公司的首选框架,国内外企业占有率持续攀升,</span>是Java工程师的必备技能。</span><span style="font-family:"background-color:#FFFFFF;">就连大名鼎鼎的阿里巴巴</span><span style="font-family:"background-color:#FFFFFF;">dubbo</span><span style="font-family:"background-color:#FFFFFF;">也正式更名为</span><span style="font-family:"background-color:#FFFFFF;">Spring Cloud Alibaba</span><span style="font-family:"background-color:#FFFFFF;">,成为了</span><span style="font-family:"background-color:#FFFFFF;">Spring Cloud </span><span style="font-family:"background-color:#FFFFFF;">微服务中的一个子模块。</span><span style="font-family:"background-color:#FFFFFF;"></span><span style="font-family:"background-color:#FFFFFF;">Spring Cloud是企业架构转型、个人能力提升、架构师进阶的不二选择。</span> </strong> </p> <p style="color:#333333;"> <strong><strong><br /> </strong> </strong> </p> <strong><span style="font-family:"color:#337FE5;font-size:14px;background-color:#FFFFFF;">【推荐你学习这门课的理由】</span><br /> </strong> <p> <br /> </p> <p> <span>1、</span><span style="color:#222226;font-family:"font-size:14px;background-color:#FFFFFF;">本课程总计</span><span style="background-color:#FFFFFF;">29</span><span style="color:#222226;font-family:"font-size:14px;background-color:#FFFFFF;">课时,<span style="color:#333333;">从微服务是什么、能够做什么开始讲起,绝对的零基础入门</span></span><span></span> </p> <p> <span style="background-color:#FFFFFF;">2、<span style="color:#333333;">课程附带全部26个项目源码,230页高清PDF正版课件</span><span style="color:#333333;"></span></span> </p> <p> <span style="background-color:#FFFFFF;"><b><br /> </b></span> </p> <p> <span style="background-color:#FFFFFF;"><b><span style="color:#337FE5;">【课程知识梳理】</span></b></span> </p> <p> <span style="background-color:#FFFFFF;"><b>1、</b></span><span style="color:#333333;">先讲解了什么是单体架构、什么是微服务架构、他们之间有什么区别和联系,各自有什么优缺点。</span> </p> <p> <span style="color:#333333;">2、</span><span style="color:#333333;">从本质入手,使用最简单的Spring Boot搭建微服务,让你认清微服务是一种思想和解决问题的手段,而不是新兴技术。</span> </p> <p style="color:#333333;"> 3、讲解Spring BootSpring Cloud 微服务架构之间的联系,原生的RestTemplate工具,以及Actuator监控端点的使用。 </p> <p style="color:#333333;"> 4、带着微服务所带来的各种优缺点,为大家引入服务发现与注册的概念和原理,从而引入我们的第一个注册中心服务Eureka。 </p> <p style="color:#333333;"> 5、引入负载均衡的理念,区分什么是服务端负载均衡,什么是客户端负载均衡,进而引入Ribbon负载均衡组件的详细使用。 </p> <p style="color:#333333;"> 6、为了解决微服务之间复杂的调用,降低代码的复杂度,我们引入了Feign声明式客户端,让你几行代码搞定服务的远程调用。 </p> <p style="color:#333333;"> 7、最后为大家介绍了整个微服务体系应该包含什么,学习路线是什么,应该学习什么。 </p> <p style="color:#333333;"> <strong><br /> </strong> </p> <p style="color:#333333;"> <strong><span style="color:#337FE5;">【</span><strong><span style="color:#337FE5;">学习方法</span></strong><span style="color:#337FE5;"></span><span style="color:#337FE5;">】</span></strong> </p> <p style="color:#333333;"> 每一节课程均有代码,最好的方式是静下心来,用一天的时间,或者两个半天时间来学习。 </p> <p style="color:#333333;"> 一边听我的讲解,一边使用我提供的项目代码进行观察和运行。 </p> <p style="color:#333333;"> 只要你能跟住我的节奏,你就可以搞定微服务。 </p> <br />
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页

打赏

wang0907

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值