Marco's Java【SpringBoot入门(四) 之 Conditional派生注解及SpringBoot自动配置原理】

前言

看完前面两节对的SpringBoot启动原理以及配置文件加载的分析之后,大家是不是已经对SpringBoot的运行机制有一个基本的了解了呢?但是这些还远远不够,往后看的朋友会发现我编写的SpringBoot入门到进阶系列的几乎每篇都会涉及到源码的分析,因为只有熟悉了SpringBoot的加载运行原理,才能更加灵活的使用SpringBoot做出自己想要的东西,在座的各位觉得呢?
(PS:当然啦,我的分析不一定是最准确的,但是想努力做到让大家能够看懂,如果有错误的地方,欢迎各位指正!)
那么本节,咱们继续学习SpringBoot的Conditional派生注解并针对SpringBoot的自动配置原理进行分析。

@Conditional派生注解

因为接下来我们的底层分析中包含大量的@Conditional派生注解,因此为了避免大家在看的时候一脸懵逼,我们还是先来了解一下基本的@Conditional派生注解有哪些,以及对应的意思是什么。

注解名称满足条件
@Conditional派生注解相关属性
@ConditionalOnJava系统的java版本是否符合要求
@ConditionalOnBean容器中存在指定Bean
@ConditionalOnMissingBean容器中不存在指定Bean
@ConditionalOnExpression满足SpEL表达式指定
@ConditionalOnClass系统中有指定的类
@ConditionalOnMissingClass系统中没有指定的类
@ConditionalOnSingleCandidate容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty系统中指定的属性是否有指定的值
@ConditionalOnResource类路径下是否存在指定资源文件
@ConditionalOnWebApplication当前是web环境
@ConditionalOnNotWebApplication当前不是web环境
@ConditionalOnJndiJNDI存在指定项

以上的注解只有在满足条件的时候,当前的配置类才会生效。

SpringBoot自动配置原理解析

之前没有接触过SpringBoot自动配置类的配置文件的朋友可能会很困惑,这些配置属性在properties文件或者yml文件该怎么写?到底哪些属性值可以被改变?
因此在分析之前给大家一个spring官方出具的关于所有自动配置类所对应的配置文件的填写指南
https://docs.spring.io/spring-boot/docs
内容很多,大家可以先点进去瞅瞅,大概就是下面这个样子。
在这里插入图片描述
其实此次分析相当于在之前的SpringBoot的启动原理之上略作补充,想要研究自动配置原理,咱们还是从 “源头” 开始调查起,虽然我们之前Debug得时候知道SpringBoot默认内置118个配置类,存在spring.factories文件中,但是到底怎么找到这个文件名字去加载呢?这还是个未知得 “谜题”,所以我们接着上回得旅程继续探险。
在这里插入图片描述
找到 “源头” 之后,咱们还是进入@EnableAutoConfiguration注解中
在这里插入图片描述

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
//利用AutoConfigurationImportSelector选择器给Spring导入一些组件
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	...
}

咱们可以查看AutoConfigurationImportSelector.class里的selectImports()方法的内容得知获取了哪些组件

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
			.loadMetadata(this.beanClassLoader);
	//获取自动配置的入口对象
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
			annotationMetadata);
	//从自动配置的入口对象中获取配置文件的名称
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

咱们重点分析getAutoConfigurationEntry(),这个方法主要做的事情就是先获取所有类上有@Configuration的配置类候选对象,并且移除掉那些可能重复的配置类以及被排除的配置类,因为我们实际在配置pom.xml的时候并没有导入并应用SpringBoot默认内置的118个配置类,因此SpringBoot默认会在此处进行一遍筛选,接着再判断配置类上是否有@OnClassCondition注解,并判断是否满足条件,再进行删选一次,当所有的候选对象都筛选完成之后,再触发广播事件AutoConfigurationImportEvent

/**
 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
 * of the importing {@link Configuration @Configuration} class.
 * @param autoConfigurationMetadata the auto-configuration metadata
 * @param annotationMetadata the annotation metadata of the configuration class
 * @return the auto-configurations that should be imported
 */
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
		AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	//获取所有的配置候选对象
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	//移除重复的配置对象
	configurations = removeDuplicates(configurations);
	//获取被排除的配置对象
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	checkExcludedClasses(configurations, exclusions);
	configurations.removeAll(exclusions);//将所有被排除的配置对象从configurations的List集合中移除
	//根据@OnClassCondition注解过滤调一些条件没有满足的配置类
	configurations = filter(configurations, autoConfigurationMetadata);
	// 找到所有需要被应用的候选配置类并触发广播事件AutoConfigurationImportEvent
	fireAutoConfigurationImportEvents(configurations, exclusions);
	return new AutoConfigurationEntry(configurations, exclusions);
}

好到这里,咱么的思路是理清楚了,但是这些候选配置对象具体从何而来呢?咱们打开getCandidateConfigurations方法,这个方法本质上就是通过SpringFactoriesLoader调用loadFactoryNames()方法从META-INF/spring.factories中获取候选对象的名称,当spring.factories文件是空文件是,会抛出异常"No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."因此,如果抛出此异常,只有两种情况,一是文件中的内容被误删除或者文件被误删了,再者就是文件的路径不对,SpringBoot在启动的时候找不到对应的文件。

/**
 * Return the auto-configuration class names that should be considered. By default
 * this method will load candidates using {@link SpringFactoriesLoader} with
 * {@link #getSpringFactoriesLoaderFactoryClass()}.
 * @param metadata the source metadata
 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
 * attributes}
 * @return a list of candidate configurations
 */
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	//扫描所有jar包类路径下的META-INF/spring.factories,把扫描到的这些文件的内容包装成properties对象
	//并从properties中获取到EnableAutoConfiguration.class类(类名)对应的值,然后把他们添加在List容器中
	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
			getBeanClassLoader());
	Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
			+ "are using a custom packaging, make sure that file is correct.");
	return configurations;
}

虽然根据上面的信息我们已经知道所有的候选对象都在META-INF/spring.factories中,但是还是想找出来他具体在哪个位置,反正也到这里了,咱们继续点开看看loadFactoryNames()方法吧~
在这里插入图片描述
loadFactoryNames()会先调用loadSpringFactories()方法加载Spring的类工厂,这个方法中有个常量FACTORIES_RESOURCE_LOCATION,值就是META-INF/spring.factories,因此loadSpringFactories()本质上就是使用classLoader类加载器去META-INF/spring.factories中加载配置文件,最后调用PropertiesLoaderUtils.loadProperties(resource)方法将我们一个个UrlResource资源(代表着factories文件中一个个配置类的资源定位)封装为properties对象,因此在后面我们讲到SpringBoot自动管理的时候,会在每一个AutoConfiguration自动配置类中找到它对应的Properties类。至于后面的getOrDefault(factoryClassName, Collections.emptyList())主要是判断loadSpringFactories(classLoader)返回过来的Map中的key(factoryClassName的工厂名称)有没有具体对应的entry,如果没有则返回空的List列表而不是返回null,避免出现空指针异常。
在这里插入图片描述

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}

	try {
		//扫描所有jar包类路径下  META-INF/spring.factories
		//把扫描到的这些文件的内容包装成properties对象
		Enumeration<URL> urls = (classLoader != null ?
				classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
		result = new LinkedMultiValueMap<>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			UrlResource resource = new UrlResource(url);
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
				String factoryClassName = ((String) entry.getKey()).trim();
				for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					result.add(factoryClassName, factoryName.trim());
				}
			}
		}
		cache.put(classLoader, result);
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

其实大家有兴趣可以自行Debug试试看,这里的result中是不是还有我们上节才学习的PropertySourceLoader(properties配置文件加载器)?这里的result对象本质上就是一个Map,从cache缓存对象中获取,如果说缓存中获取的result不为空则直接返回result对象。
在这里插入图片描述
点开PropertySourceLoader之后是不是发现咱们的两个 “老朋友” ?显然SpringBoot将PropertiesPropertySourceLoader和YamlPropertySourceLoader都作为配置文件资源加载器储存在同一个Map集合中了,key都为PropertySourceLoader
在这里插入图片描述

自动配置类进行自动配置功能

想了解自动配置,咱们得先找个 “模板” 吧,这里就以我们熟悉得HttpEncodingAutoConfiguration(编码自动配置器)为例子。大家先不要看下面得代码部分,仔细阅读一下类上得注解

@Configuration//代表这是一个配置文件
@EnableConfigurationProperties(HttpProperties.class) //启动配置文件的配置类
//代表启用这个配置文件,让它生效,必须是SERVLET的运行环境
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//表示想要这个配置文件生效,必须要有CharacterEncodingFilter这个类
@ConditionalOnClass(CharacterEncodingFilter.class)
//如果配置了spring.http.encoding,那么当条件不成立,配置不生效 ,默认为true生效
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
	//它已经和SpringBoot的配置文件映射了,就是上面封装的properties类
	private final HttpProperties.Encoding properties;
	//只有一个有参构造器的情况下,参数的值就会从容器中拿
	public HttpEncodingAutoConfiguration(HttpProperties properties) {
		this.properties = properties.getEncoding();
	}
	//创建过滤器对象
	@Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取
	@ConditionalOnMissingBean //判断容器没有CharacterEncodingFilter这个组件?没有的话,此配置类不会生效
	public CharacterEncodingFilter characterEncodingFilter() {
		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
		filter.setEncoding(this.properties.getCharset().name());//设置编码集
		filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));//对请求设置编码
		filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));//对相应设置编码
		return filter;
	}
}

所有在配置文件中能配置的属性都是在xxxxProperties类中封装着,因此配置文件配置什么,咱们就可以根据文件中的属性头,例如下方的spring.http.encoding找到对应的属性类,如HttpEncodingProperties,然后通过http.encoding找到这个类对应的配置属性,就可以通过.properties文件或者.yml文件对我们的类的属性进行自定义配置啦!是不是相当灵活~

//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties {
	public static final Charset DEFAULT_CHARSET;
总结:
  • SpringBoot启动的时候会加载大量的自动配置类,这些配置类都源自spring.factories文件
  • 在创建项目之初,我们需要的功能有没有SpringBoot默认写好的自动配置类,有的话就不用配置啦,当然重复配置了,SpringBoot也会帮我们过滤筛选掉
  • 接着再来看这个自动配置类中到底配置了哪些组件(只要我们要用的组件有,就不需要再来配置了
  • 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可
    以在配置文件中指定这些属性的值
    • xxxxAutoConfigurartion:一般自动配置类的结尾都是AutoConfigurartion,用于给容器中添加组件
    • xxxxProperties:一般自动配置类中都包含Properties类,封装配置文件中的默认配置
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值