SpringBoot自动配置原理
本文将通过代码,解读SpringBoot自动配置的原理。在学习自动配置之前,首先要先理解什么是SpringBoot的自动配置?
我们知道,SpringBoot是一种方便开发者开发应用程序的框架,可以用尽可能少的配置(即习惯优于配置)进行开发,这是在Spring4.X后出现的。通过这种习惯优于配置的方式,我们可以很轻松集成各种如注册中心,消息组件,redes等主流的框架到我们的应用当中(可以理解为可以轻松集成一个个组件框架的starter),但是很显然每一个组件框架能被轻松集成到应用中,其背后就是SpringBoot帮我们自动完成了一些约定俗成的配置并将其发布,下面就让我们研究一下SpringBoot是怎么实现自动配置的,以及都有哪些可以自动配置的框架。
(一)SpringBoot自动配置原理之启动类@SpringBootApplication
通过前面简单应用的样例,我们知道只需要通过一个添加了@SpringBootApplication注解的配置启动类,就可以完成自动配置,那么很明显自动配置的操作应该是在@SpringBootApplication这个注解当中。
让我们看看这个@SpringBootApplication注解是何方神圣?点开,可以看到这是一个组合注解,同时拥有以**解:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
其中,最后3个注解是最重要的注解:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan。
对于@SpringBootConfiguration实际上是一个配置类注解@SpringBootConfiguration,也就是表明这个类是一个配置类;
对于@ComponentScan我们前面应用文章也知道这是包扫描注解,会默认扫描当前配置类所在包及子包下的所有文件类:
其中带了(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) 表示在扫描过程中,会忽视所有实现了TypeExcludeFilter接口以及AutoConfigurationExcludeFilter接口的类,这两种类型本身实现了TypeFilter接口,而TypeFilter接口实现类就是指需要剔除不加载的类,因此参数就是进行剔除跳过指定类扫描的意思;
对于@EnableAutoConfiguration,这个顾名思义就是我们要重点了解的“自动配置”注解了。
那么这个@EnableAutoConfiguration又是一个实现什么功能含义的注解呢?点开这个注解查看:
可以看到,这个注解通过@Import引入了一个非常关键的类
AutoConfigurationImportSelector.class
这一行注解的意思就是,在这个@EnableAutoConfiguration注解生效之前引入生效,由于这个@EnableAutoConfiguration修饰在@SpringBootApplication,@SpringBootApplication修饰在SpringbootMyspringApplication配置启动类,因此最终就是当SpringbootMyspringApplication配置启动类加载被Spring容器管理之前,容器中就已经引入了AutoConfigurationImportSelector.class的实例,简而言之就是在SpringbootMyspringApplication配置启动类被加载之前就以前引入加载了AutoConfigurationImportSelector实例。
那么很显然,这个AutoConfigurationImportSelector实例顾名思义就是一个自动配置引入类选择器。可以看到这个AutoConfigurationImportSelector的继承关系图实际上就是一个实现了ImportSelector的类并且重写了其selectImports方法,在这个方法中,就完成自动配置的逻辑。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//判断是否开启AutoConfiguration
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
我们一行行代码解析这个selectImports都做了什么事情?首选从这个方法可以看到传入参数是一个AnnotationMetadata对象,返回值是一个String[]数组。AnnotationMetadata对象这是一个注解元数据对象,这个对象包含了启动配置类所定义的一些元数据信息(例如是否开启自动配置,包含哪些剔除类等等)。
(1)首先是第一行代码:第一行代码只要用于判断是否开启AutoConfiguration,默认为true:
(2)接下来就是第二行代码:
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader);
这行代码实际上是通过类加载器,去一个名为META-INF/spring-autoconfigure-metadata.properties的配置文件中以K-V方式加载文件中定义好的全部可配置类信息,最终载入到AutoConfigurationMetadata对象中:
这个AutoConfigurationMetadata对象的数据会用于下一步读取可配置类集合后进行match比对过滤。
(3)加下来是核心代码第三行:
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
这行代码就是SpringBoot预先加载所有可配置的JavaConfig配置类核心代码。点开代码查看:
核心在
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
这个方法就是返回全部可配置的Config配置类,让我们看看这个方法内部实现细节,调用链如下:
-->AutoConfigurationImportSelector.getCandidateConfigurations -->AutoConfigurationImportSelector.getCandidateConfigurations -->SpringFactoriesLoader.loadFactoryNames -->SpringFactoriesLoader.loadSpringFactories -->Properties properties = PropertiesLoaderUtils.loadProperties(resource);
核心逻辑在SpringFactoriesLoader.loadSpringFactories方法:
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
这行代码的作用是解析工程中有哪些可以加载可配置Config类的url资源文件地址,默认会从下面地址获取:
springboot的jar包 org.springframework.boot:spring-boot-autoconfigure:2.2.5.RELEASE 中的 META-INF/spring.factories
接下来,拿到这些可能存在可配置类的地址后,就会对这些地址对应的配置文件资源进行加载解析。解析的过程实际上是将所有预先定义可配置的javaConfig配置类,以K-V键值对的方式读取出来并Map.Entry<?, ?>当中,最终转化为List返回。
这个预定义的可配置类信息表的结构是一个类似Map<K,V>数据结构,每个K会对应一个V集合,在这个配置表中我们可以看到很多熟悉的可配置框架比如SpringMVC,SpringData等框架的Config:
# AutoConfigureWebMvc auto-configuration imports org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc=\ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
在读取这个配置文件内的信息放入Map之前,首先通过下面代码,将配置文件中所有的Key和value全部解析放在Properties 对象当中:
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
根据META-INF/spring.factories定义7种不同类型的Key,如下:
org.springframework.context.ApplicationContextInitializer=\ org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
解析完成并封装之后,最终进行遍历操作转化为一个Map<String, List<String>>数据结构的对象返回:
for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } }
转化完成之后,对配置的一些可剔除类进行剔除操作,然后将剩下可进行配置的Config信息封装到AutoConfigurationEntry中:
(4)最后一行代码:
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
最后一行代码的作用是将前面从可配置Config类预定义的配置文件中读取到的可配置类Entry对象,转化为String[]数组返回出去,这个String[]数据的数据就是一个个Config类。
至此,整个SpringBoot自动配置原理的过程就结束了,说白了自动配置原理只是在SpringBoot启动时预先将一些帮开发者写好的可直接配置的JavaConfig配置类,通过String[] selectImports()方法解析后返回交给Spring,在后面真正使用时通过相关pom依赖引入starter等配置使这些JavaConfig生效,这仅仅只是预加载过程,还没有到真正的配置使用过程,真正的使用过程后面会以WebMVC的自动配置进行介绍(涉及到starter组件)
总结如下:
/**
* springboot自动装配原理:
* 1.springboot @EnableAutoConfiguration中AutoConfigurationImportSelector类执行selectImports(),
* 从这里可以看到自动装配工程位于springboot的jar包org.springframework.boot:spring-boot-autoconfigure:2.2.5.RELEASE。
* 根据selectImports()源码解析META/INF/spring.factories定义的所有javaConfig类,返回class对象限定名数组给spring,
* 其中包括springmvc的javaConfig配置等等
*/
到这里前面还留下一个问题,就是pringBoot启动时预先将一些帮开发者写好的可直接配置的JavaConfig配置类,通过String[] selectImports()方法解析后返回交给Spring,这个只是一个结论,那么AutoConfigurationImportSelector的selectImports到底是什么时候被Spring执行触发的呢?这个上面并没有做介绍,因为这个是Spring的知识,下面进行简单的调用链介绍,我目前也还没有深入了解到这一块调用的原理,仅仅只是大概读了部分核心调用链源码但是还没有搞明白完整调用过程,下面将网友的总结进行归纳,详细源码请自行百度这一块知识了解。
2. AutoConfigurationImportSelector的调度:(Spring知识)
context.annotation.ConfigurationClassParser类的parse() 方法
-> 调用每个实现了DeferredImportSelector接口的实现类(如SpringBoot启动类)对应DeferredImportSelectorGroupingHandler的processGroupImports()方法
| -> 调用processImports()方法来处理所有@Import注解
| -> 遍历每个@Import标签,生成被注入的ImportSelector子类的实例
| -> 对于普通ImportSelector,调用其selectImport()方法,筛掉exclude的,再嵌套processImports(),对需要被@Import的类的@Import注解进行处理
| -> 对于DefferedImportSelector,只将DeferredImportSelectorHolder加入到deferredImportSelectorsHandler列表
-> 然后继续往后执行对defferedImportImportSelectors调用相应handler的process()方法进行处理
-> 对DefferedImportImportSelector调用processImports()