( 八 ) SpringBoot 自动配置原理解析
( 八 ) SpringBoot 自动配置原理解析
1、简介
我们知道,Spring Boot 项目创建完成后,即使不进行任何的配置,也能够顺利地运行,这都要归功于 Spring Boot 的自动化配置。
Spring Boot 默认使用 application.properties 或 application.yml 作为其全局配置文件,我们可以在该配置文件中对各种自动配置属性(server.port、logging.level.* 、spring.config.active.no-profile 等等)进行修改,并使之生效,那么您有没有想过这些属性是否有据可依呢?答案是肯定的。
Spring Boot 官方文档:常见应用属性 中对所有的配置属性都进行了列举和解释,我们可以根据官方文档对 Spring Boot 进行配置,但 Spring Boot 中的配置属性数量庞大,仅仅依靠官方文档进行配置也十分麻烦。我们只有了解了 Spring Boot 自动配置的原理,才能更加轻松熟练地对 Spirng Boot 进行配置。本节为你揭开 SpringBoot 自动配置的神秘面纱。
2、SpringBoot 基于 spring-boot-autoconfigure-2.0.3.RELEASE.jar 进行自动配置
Spring Boot 自动化配置也是基于 Spring Factories 机制实现的,在 spring-boot-autoconfigure-xxx.jar 类路径下的 META-INF/spring.factories 中设置了 Spring Boot 自动配置的内容
jar包依赖关系如下:
说明我们创建一个web项目, 就会以引入自动配置的依赖
3、Spring Factories 机制
Spring Boot 的自动配置是基于 Spring Factories 机制实现的。
Spring Factories 机制是 Spring Boot 中的一种服务发现机制,这种扩展机制与 Java SPI 机制十分相似。Spring Boot 会自动扫描所有 Jar 包类路径下 META-INF/spring.factories 文件,并读取其中的内容,进行实例化,这种机制也是 Spring Boot Starter 的基础。
spring.factories
spring.factories 文件本质上与 properties 文件相似,其中包含一组或多组键值对(key=vlaue),其中,key 的取值为接口的完全限定名;value 的取值为接口实现类的完全限定名,一个接口可以设置多个实现类,不同实现类之间使用“,”隔开,例如:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
注意:文件中配置的内容过长,为了阅读方便而手动换行时,为了防止内容丢失可以使用“\”。
4、Spring Factories 实现原理
spring-core 包里定义了 SpringFactoriesLoader 类,这个类会扫描所有 Jar 包类路径下的 META-INF/spring.factories 文件,并获取指定接口的配置。在 SpringFactoriesLoader 类中定义了两个对外的方法,如下表。
返回值 | 方法 | 描述 |
<T> List<T> | loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) | 静态方法; 根据接口获取其实现类的实例; 该方法返回的是实现类对象列表。 |
List<String> | loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) | 公共静态方法; 根据接口l获取其实现类的名称; 该方法返回的是实现类的类名的列表 |
以上两个方法的关键都是从指定的 ClassLoader 中获取 spring.factories 文件,并解析得到类名列表,具体代码如下。
loadFactories() 方法能够获取指定接口的实现类对象,具体代码如下:
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryType, "'factoryType' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 调用loadFactoryNames获取接口的实现类
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
// 遍历 factoryNames 数组,创建实现类的对象
List<T> result = new ArrayList(factoryImplementationNames.size());
Iterator var5 = factoryImplementationNames.iterator();
//排序
while(var5.hasNext()) {
String factoryImplementationName = (String)var5.next();
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
loadFactoryNames() 方法能够根据接口获取其实现类类名的集合,具体代码如下:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
//获取自动配置类
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
loadSpringFactories() 方法能够读取该项目中所有 Jar 包类路径下 META-INF/spring.factories 文件的配置内容,并以 Map 集合的形式返回,具体代码如下:
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
//扫描所有 Jar 包类路径下的 META-INF/spring.factories 文件
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
//将扫描到的 META-INF/spring.factories 文件中内容包装成 properties 对象
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Map.Entry<?, ?> entry = (Map.Entry)var6.next();
//提取 properties 对象中的 key 值
String factoryTypeName = ((String)entry.getKey()).trim();
//提取 proper 对象中的 value 值(多个类的完全限定名使用逗号连接的字符串)
// 使用逗号为分隔符转换为数组,数组内每个元素都是配置类的完全限定名
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
//遍历配置类数组,并将数组转换为 list 集合
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
//将 propertise 对象的 key 与由配置类组成的 List 集合一一对应存入名为 result 的 Map 中
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
//返回 result
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
5、@SpringBootApplication 注解
所有 Spring Boot 项目的主启动程序类上都使用了一个 @SpringBootApplication 注解,该注解是 Spring Boot 中最重要的注解之一 ,也是 Spring Boot 实现自动化配置的关键。
@SpringBootApplication 是一个组合元注解,其主要包含两个注解:@SpringBootConfiguration 和 @EnableAutoConfiguration,其中 @EnableAutoConfiguration 注解是 SpringBoot 自动化配置的核心。
- @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制。
- @SpringBootConfiguration(实际是:@Configuration):允许在上下文中注册额外的 bean 或导入其他配置类。
- @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如下图所示,容器中将排除TypeExcludeFilter和AutoConfigurationExcludeFilter。
@EnableAutoConfiguration 注解
@EnableAutoConfiguration 注解用于开启 Spring Boot 的自动配置功能, 它使用 Spring 框架提供的 @Import 注解通过 AutoConfigurationImportSelector类(选择器)给容器中导入自动配置组件。
AutoConfigurationImportSelector 类
AutoConfigurationImportSelector 类实现了 DeferredImportSelector 接口,AutoConfigurationImportSelector 中还包含一个静态内部类 AutoConfigurationGroup,它实现了 DeferredImportSelector 接口的内部接口 Group(Spring 5 新增)。
AutoConfigurationImportSelector 类中包含 3 个方法,如下表:
返回值 | 方法声明 | 描述 | 内部类方法 | 内部类 |
Class<? extends Group> | getImportGroup() | 该方法获取实现了 Group 接口的类,并实例化 | 否 | |
void | process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) | 该方法用于引入自动配置的集合 | 是 | AutoConfigurationGroup |
Iterable<Entry> | selectImports() | 遍历自动配置类集合(Entry 类型的集合),并逐个解析集合中的配置类 | 是 | AutoConfigurationGroup |
AutoConfigurationImportSelector 内各方法执行顺序如下。
- getImportGroup() 方法
- process() 方法
- selectImports() 方法
下面我们将分别对以上 3 个方法及其调用过程进行介绍。
1. getImportGroup() 方法
AutoConfigurationImportSelector 类中 getImportGroup() 方法主要用于获取实现了 DeferredImportSelector.Group 接口的类,代码如下
public Class<? extends Group> getImportGroup() {
//获取实现了 DeferredImportSelector.Gorup 接口的 AutoConfigurationImportSelector.AutoConfigurationGroup 类
return AutoConfigurationImportSelector.AutoConfigurationGroup.class;
}
2. process() 方法
静态内部类 AutoConfigurationGroup 中的核心方法是 process(),该方法通过调用 getAutoConfigurationEntry() 方法读取 spring.factories 文件中的内容,获得自动配置类的集合,代码如下
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
return String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
});
//拿到 META-INF/spring.factories中的EnableAutoConfiguration,并做排除、过滤处理
//AutoConfigurationEntry里有需要引入配置类和排除掉的配置类,最终只要返回需要配置的配置类
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry =
((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
//加入缓存,List<AutoConfigurationEntry>类型
this.autoConfigurationEntries.add(autoConfigurationEntry);
Iterator var4 = autoConfigurationEntry.getConfigurations().iterator();
while(var4.hasNext()) {
String importClassName = (String)var4.next();
//加入缓存,Map<String, AnnotationMetadata>类型
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
getAutoConfigurationEntry() 方法通过调用 getCandidateConfigurations() 方法来获取自动配置类的完全限定名,并在经过排除、过滤等处理后,将其缓存到成员变量中,具体代码如下
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
//获取注解元数据中的属性设置
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//获取自动配置类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//删除list 集合中重复的配置类
configurations = this.removeDuplicates(configurations);
//获取飘出导入的配置类
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
//检查是否还存在排除配置类
this.checkExcludedClasses(configurations, exclusions);
//删除排除的配置类
configurations.removeAll(exclusions);
//获取过滤器,过滤配置类
configurations = this.getConfigurationClassFilter().filter(configurations);
//出发自动化配置导入事件
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
在 getCandidateConfigurations() 方法中,根据 Spring Factories 机制调用 SpringFactoriesLoader 的 loadFactoryNames() 方法,根据 EnableAutoConfiguration.class (自动配置接口)获取其实现类(自动配置类)的类名的集合,如下图。
3. process() 方法
以上所有方法执行完成后,AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports() 会将 process() 方法处理后得到的自动配置类,进行过滤、排除,最后将所有自动配置类添加到容器中。到这里可能面试官会问你:“spring.factories中这么多配置,每次启动都要全部加载么?”。很明显,这是不现实的,@ConditionalOnXXX 中的所有条件都满足,该类才会生效。
6、条件注解使自动配置的生效和修改
spring.factories 文件中的所有自动配置类(xxxAutoConfiguration),都是必须在一定的条件下才会作为组件添加到容器中,配置的内容才会生效。这些限制条件在 Spring Boot 中以 @Conditional 派生注解的形式体现,如下表
注解 | 生效条件 |
@ConditionalOnJava | 应用使用指定的 Java 版本时生效 |
@ConditionalOnBean | 容器中存在指定的 Bean 时生效 |
@ConditionalOnMissingBean | 容器中不存在指定的 Bean 时生效 |
@ConditionalOnExpression | 满足指定的 SpEL 表达式时生效 |
@ConditionalOnClass | 存在指定的类时生效 |
@ConditionalOnMissingClass | 不存在指定的类时生效 |
@ConditionalOnSingleCandidate | 容器中只存在一个指定的 Bean 或这个 Bean 为首选 Bean 时生效 |
@ConditionalOnProperty | 系统中指定属性存在指定的值时生效 |
@ConditionalOnResource | 类路径下存在指定的资源文件时生效 |
@ConditionalOnWebApplication | 当前应用是 web 应用时生效 |
@ConditionalOnNotWebApplication | 当前应用不是 web 应用生效 |