【课程免费分享】5-@EnableAutoConfiguration原理实战

5、@EnableAutoConfiguration原理实战

在SpringBoot项目中集成其他框架是非常简单的,如果需要添加WebMvc,只需要引入对应的依赖

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

就可以了,这样就轻轻松松的把WebMvc给整合进来了,是不是超简单。就连@EnableXX注解都不需要,那是为什么呢?是时候来剖析其中的原理了。


SpringBoot项目中通常是添加注解@SpringBootApplication,这个注解集成了常用的几个注解:

@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan

如果只是单纯的启动SpringBoot项目的话,只需要添加@SpringBootConfiguration注解就可以了。这样项目虽然可以正常启动和使用,但却就失去了SpringBoot给我们带来的便利性。SpringBoot整合其他框架通常会有各种AutoConfiguration,但是必须得添加@EnableAutoConfiguration注解才可以使用。这里来解析下:

在SpringBoot官方文档(https://docs.spring.io/spring-boot/docs/1.5.8.RELEASE/reference/htmlsingle/#using-boot-starter-poms)中,特别说明了META-INF/spring.factories目录的使用

43.1 Understanding auto-configured beans
Under the hood, auto-configuration is implemented with standard @Configuration classes. Additional @Conditional annotations are used to constrain when the auto-configuration should apply. Usually auto-configuration classes use @ConditionalOnClass and @ConditionalOnMissingBean annotations. This ensures that auto-configuration only applies when relevant classes are found and when you have not declared your own @Configuration.

You can browse the source code of spring-boot-autoconfigure to see the @Configuration classes that we provide (see the META-INF/spring.factories file).

43.2 Locating auto-configuration candidates
Spring Boot checks for the presence of a META-INF/spring.factories file within your published jar. The file should list your configuration classes under the EnableAutoConfiguration key.

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
You can use the @AutoConfigureAfter or @AutoConfigureBefore annotations if your configuration needs to be applied in a specific order. For example, if you provide web-specific configuration, your class may need to be applied after WebMvcAutoConfiguration.

If you want to order certain auto-configurations that shouldn’t have any direct knowledge of each other, you can also use @AutoconfigureOrder. That annotation has the same semantic as the regular @Order annotation but provides a dedicated order for auto-configuration classes.

大致意思是说SpringBoot会自动解析所有jar中的META-INF/spring.factories文件。其中大部分自动配置文件都放在了spring-boot-autoconfigure的jar中,可以根据自己的需要去看看。如果需要指定自动配置类的顺序,可以使用 @AutoConfigureAfter @AutoConfigureBefore、@AutoconfigureOrder进行设置顺序。

那么来解析下@EnableAutoConfiguration注解做了什么。

@EnableAutoConfiguration

首先进入注解对应的源码中:

@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
//略。。。
}

发现这里使用了@Import注解,导入EnableAutoConfigurationImportSelector类

@Import 可以将对应的bean导入到Spring上下文中。如果类在工程中的话那么直接使用@Configuration注解即可,Spring会自动识别的。但是如果在其他jar包或框架上,没有配置到自动扫描的目录中或者是没有添加@Configuration注解,那么就需要使用@Import将其导入到项目中来。

EnableAutoConfigurationImportSelector继承了AutoConfigurationImportSelector,实现了isEnable方法,当配置文件中配置spring.boot.enableautoconfiguration=false的时候,@EnableAutoConfiguration 功能为关闭状态,不进行其他自动逻辑处理。也就是所有的EnableXX框架都不能自动配置启动了。

ImportSelector

核心作用就是:将方法selectImports中返回的类数组导入到Spring上下文中。

AutoConfigurationImportSelector间接的实现了ImportSelector接口,且实现为:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
  //配置spring.boot.enableautoconfiguration=false的时候不导入任何bean
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	try {
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);
		configurations = removeDuplicates(configurations);
		configurations = sort(configurations, autoConfigurationMetadata);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return configurations.toArray(new String[configurations.size()]);
	}
	catch (IOException ex) {
		throw new IllegalStateException(ex);
	}
}

以上这部分代码就是自动配置的核心了,下面对以上的代码进行逐步分析:

  • AutoConfigurationMetadata autoConfigurationMetadata =
    AutoConfigurationMetadataLoader
    .loadMetadata(this.beanClassLoader);

这里读取了META-INF/spring-autoconfigure-metadata.properties配置文件,这个配置文件中配置了SpringBoot自动集成的各种Enable框架的执行条件,比如定义与其他AutoConfiguration框架的执行顺序,需要哪些bean在的时候才可以执行等。这里的功能就等价于@AutoConfigureAfter
@AutoConfigureBefore注解的功能。下面截取部分配置,感兴趣的可以到spring-boot-autoconfig-版本号/META-INF/spring-autoconfigure-metadata.properties文件中查看。
enter image description here

  • getCandidateConfigurations(annotationMetadata,
    attributes);

这里读取META-INF/spring.factories配置文件中对应key:org.springframework.boot.autoconfigure.EnableAutoConfiguration所对应的类。

源码如下:

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
		AnnotationAttributes attributes) {
	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;
}

SpringFactoriesLoader.loadFactoryNames方法中主要是加载META-INF/spring.factories文件,并且获取key为EnableAutoConfiguration类全名对应的属性值。感兴趣的可以继续跟入看看源码。

  • AnnotationAttributes attributes = getAttributes(annotationMetadata);

这里读取@EnableAutoConfiguration注解中配置的exclude,excludeName两个属性值

  • configurations = removeDuplicates(configurations);

去除重复的引用,这里实现相当的简单,先将list转换有序的set对象,这样的话重复的类就被自动剔除了,然后再将set转换成list。

  • configurations = sort(configurations, autoConfigurationMetadata);

顾名思义,这里是对所有的自动配置bean进行排序,使用的规则就是在上面获取到的配置文件的autoConfigurationMetadata。

sort的实现为:

	private List<String> sort(List<String> configurations,
		AutoConfigurationMetadata autoConfigurationMetadata) throws IOException {
	configurations = new AutoConfigurationSorter(getMetadataReaderFactory(),
			autoConfigurationMetadata).getInPriorityOrder(configurations);
	return configurations;
}

实际的排序功能是在getInPriorityOrder中

	public List<String> getInPriorityOrder(Collection<String> classNames) {
	final AutoConfigurationClasses classes = new AutoConfigurationClasses(
			this.metadataReaderFactory, this.autoConfigurationMetadata, classNames);
	List<String> orderedClassNames = new ArrayList<String>(classNames);
	// Initially sort alphabetically
	Collections.sort(orderedClassNames);
	// Then sort by order
	Collections.sort(orderedClassNames, new Comparator<String>() {

		@Override
		public int compare(String o1, String o2) {
			int i1 = classes.get(o1).getOrder();
			int i2 = classes.get(o2).getOrder();
			return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
		}

	});
	// Then respect @AutoConfigureBefore @AutoConfigureAfter
	orderedClassNames = sortByAnnotation(classes, orderedClassNames);
	return orderedClassNames;
}

这里的实现还是比较清晰的,先通过类名自然排序,然后根据bean配置的order排序(@AutoConfigureOrder或在配置文件中指定),最后根据@AutoConfigureBefore @AutoConfigureAfter注解中配置(配置文件中指定)的顺序关系进行排序。通过以上步骤,就将所有AutoConfiguration的顺序指定ok了。

  • Set exclusions = getExclusions(annotationMetadata, attributes);

获取EnableAutoConfiguration注解中配置的exclude,excludeName与配置中配置的spring.autoconfigure.exclude对应的类

  • checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);

先对之前步骤获取到的需要剔除的类进行是否存在校验,如果在所有的AutoConfiguration(configurations)中都不包含配置的类的话,那么说明配置有问题,直接抛出异常。如果都存在的话,那么从configurations去除需要排除的类。

  • configurations = filter(configurations, autoConfigurationMetadata);

进行数据过滤。这里会获取系统中所有的AutoConfigurationImportFilter对象,通过循环调用AutoConfigurationImportFilter.match方法筛选出不符合条件的AutoConfiguration类。这样流程过后剩下的AutoConfiguration类就是符合我们系统要求的了。

  • fireAutoConfigurationImportEvents(configurations, exclusions);

发送自动配置筛选完成事件(AutoConfigurationImportEvent),将筛选后的结果,通知对应的(实现了AutoConfigurationImportListener)的监听者,进行对应的操作。

通过以上步骤,筛选出了符合需要的自动配置的类。针对以上步骤整理出的流程图如下:
enter image description here

总结

@EnableAutoConfiguration会自动将工程中META-INF/spring.factories配置文件中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的所有类进行自动导入。这也就是为什么@EnableAutoConfiguration是SpringBoot项目的标配注解了,如果没有导入这个注解所实现的功能,那么所有的自动配置功能将无法使用,也就失去了SpringBoot的方便性了。
如果需要配置自动配置类的加载顺序,可以在META-INF/spring-autoconfigure-metadata.properties进行配置 。这里就是为什么有的框架只需要引入对应的jar就可以自动运行的原因了(如web)。

实战

有两个自动配置类TestConfiguration,TestConfiguration2代码基本一样。只在构造方法中打印出实例化后的类名和init信息,如下:

public class TestConfiguration {
private static Logger log = LoggerFactory.getLogger(TestConfiguration.class);

public TestConfiguration() {
	log.info("=========>TestConfiguration init!!!");
}

}

然后将这两个类配置到工程中的META-INF/spring.factories文件中:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.cml.chat.lesson.lesson5.TestConfiguration,com.cml.chat.lesson.lesson5.TestConfiguration2

默认bean的加载是按照类名的自然排序进行的,项目启动后输入的log为:

com.cml.chat.lesson.lesson5.TestConfiguration - =========>TestConfiguration init!!!
com.cml.chat.lesson.lesson5.TestConfiguration2 - =========>TestConfiguration2 init!!!

此时如果想要TestConfiguration2优先于TestConfiguration执行,比如TestConfiguration需要依赖TestConfiguration2做的操作。那么这时候就可以在META-INF/spring-autoconfigure-metadata.properties添加配置

#order config
com.cml.chat.lesson.lesson5.TestConfiguration=
com.cml.chat.lesson.lesson5.TestConfiguration2=
com.cml.chat.lesson.lesson5.TestConfiguration.AutoConfigureAfter=com.cml.chat.lesson.lesson5.TestConfiguration2

项目启动后输入log:

com.cml.chat.lesson.lesson5.TestConfiguration2 - =========>TestConfiguration2 init!!!
com.cml.chat.lesson.lesson5.TestConfiguration - =========>TestConfiguration init!!!

TestConfiguration在TestConfiguration2初始化之后了,这里可以完成注解对应的顺序功能和条件限制功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值