SpringBoot是如何实现自动配置的? SpringBoot源码(四)


SpringBoot是如何实现自动配置的? SpringBoot源码(四)

注:该源码分析对应SpringBoot版本为2.1.0.RELEASE

1 前言

本篇接 助力SpringBoot自动配置的条件注解原理揭秘 SpringBoot源码(三)

温故而知新,我们来简单回顾一下上篇的内容,上一篇我们分析了SpringBoot的条件注解@ConditionalOnXxx的相关源码,现挑重点总结如下:

SpringBoot的所有@ConditionalOnXxx的条件类OnXxxCondition都是继承于SpringBootCondition基类,而SpringBootCondition又实现了Condition接口。
SpringBootCondition基类主要用来打印一些条件注解评估报告的日志,这些条件评估信息全部来源于其子类注解条件类OnXxxCondition,因此其也抽象了一个模板方法getMatchOutcome留给子类去实现来评估其条件注解是否符合条件。
前一篇我们也还有一个重要的知识点还没分析,那就是跟过滤自动配置类逻辑有关的AutoConfigurationImportFilter接口,这篇文章我们来填一下这个坑。
前面我们分析了跟SpringBoot的自动配置息息相关内置条件注解@ConditionalOnXxx后,现在我们就开始来撸SpringBoot自动配置的相关源码了。


2 @SpringBootApplication注解

在开始前,我们先想一下,SpringBoot为何一个标注有@SpringBootApplication注解的启动类通过执行一个简单的run方法就能实现SpringBoot大量Starter的自动配置呢? 其实SpringBoot的自动配置就跟@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) })
public @interface SpringBootApplication {
   
    // ...省略非关键代码
}

@SpringBootApplication标注了很多注解,我们可以看到其中跟SpringBoot自动配置有关的注解就有一个即@EnableAutoConfiguration,因此,可以肯定的是SpringBoot的自动配置肯定跟@EnableAutoConfiguration息息相关(其中@ComponentScan注解的excludeFilters属性也有一个类AutoConfigurationExcludeFilter,这个类跟自动配置也有点关系,但不是我们关注的重点)。 现在我们来打开@EnableAutoConfiguration注解的源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
   
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {
   };
    String[] excludeName() default {
   };
}

看到@EnableAutoConfiguration注解又标有@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)两个注解,顾名思义,@AutoConfigurationPackage注解肯定跟自动配置的包有关,而AutoConfigurationImportSelector则是跟SpringBoot的自动配置选择导入有关(Spring中的ImportSelector是用来导入配置类的,通常是基于某些条件注解@ConditionalOnXxxx来决定是否导入某个配置类)。

因此,可以看出AutoConfigurationImportSelector类是我们本篇的重点,因为SpringBoot的自动配置肯定有一个配置类,而这个配置类的导入则需要靠AutoConfigurationImportSelector这个哥们来实现。

接下来我们重点来看AutoConfigurationImportSelector这个类,完了我们再简单分析下@AutoConfigurationPackage这个注解的逻辑。

3 如何去找SpringBoot自动配置实现逻辑的入口方法?

可以肯定的是SpringBoot的自动配置的逻辑肯定与AutoConfigurationImportSelector这个类有关,那么我们该如何去找到SpringBoot自动配置实现逻辑的入口方法呢?

在找SpringBoot自动配置实现逻辑的入口方法前,我们先来看下AutoConfigurationImportSelector的相关类图,好有个整体的理解。看下图:
在这里插入图片描述

可以看到AutoConfigurationImportSelector重点是实现了DeferredImportSelector接口和各种Aware接口,然后DeferredImportSelector接口又继承了ImportSelector接口。

自然而然的,我们会去关注AutoConfigurationImportSelector复写DeferredImportSelector接口的实现方法selectImports方法,因为selectImports方法跟导入自动配置类有关,而这个方法往往是程序执行的入口方法。经过调试发现selectImports方法很具有迷惑性,selectImports方法跟自动配置相关的逻辑有点关系,但实质关系不大。

此时剧情的发展好像不太符合常理,此时我们又该如何来找到自动配置逻辑有关的入口方法呢?

最简单的方法就是在AutoConfigurationImportSelector类的每个方法都打上断点,然后调试看先执行到哪个方法。但是我们可以不这么做,我们回想下,自定义一个Starter的时候我们是不是要在spring.factories配置文件中配置

EnableAutoConfiguration=XxxAutoConfiguration

因此可以推断,SpringBoot的自动配置原理肯定跟从spring.factories配置文件中加载自动配置类有关,于是结合AutoConfigurationImportSelector的方法注释,我们找到了getAutoConfigurationEntry方法。于是我们在这个方法里面打上一个断点,此时通过调用栈帧来看下更上层的入口方法在哪里,然后我们再从跟自动配置相关的更上层的入口方法开始分析。
在这里插入图片描述

通过图我们可以看到,跟自动配置逻辑相关的入口方法在DeferredImportSelectorGrouping类的getImports方法处,因此我们就从DeferredImportSelectorGrouping类的getImports方法来开始分析SpringBoot的自动配置源码好了。

4 分析SpringBoot自动配置原理

既然找到ConfigurationClassParser.getImports()方法是自动配置相关的入口方法,那么下面我们就来真正分析SpringBoot自动配置的源码了。

先看一下getImports方法代码:

// ConfigurationClassParser.java
public Iterable<Group.Entry> getImports() {
   
    // 遍历DeferredImportSelectorHolder对象集合deferredImports,deferredImports集合装了各种ImportSelector,当然这里装的是AutoConfigurationImportSelector
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
   
        // 【1】,利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定导入哪些配置类(这个是我们分析的重点,自动配置的逻辑全在这了)
        this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                           deferredImport.getImportSelector());
    }
    // 【2】,经过上面的处理后,然后再进行选择导入哪些配置类
    return this.group.selectImports();
}

标【1】处的的代码是我们分析的重中之重,自动配置的相关的绝大部分逻辑全在这里了,将在4.1 分析自动配置的主要逻辑深入分析。那么this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());主要做的事情就是在this.group即AutoConfigurationGroup对象的process方法中,传入的AutoConfigurationImportSelector对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类,就是这么个事情,无他。

注:

  1. AutoConfigurationGroup:是AutoConfigurationImportSelector的内部类,主要用来处理自动配置相关的逻辑,拥有process和selectImports方法,然后拥有entries和autoConfigurationEntries集合属性,这两个集合分别存储被处理后的符合条件的自动配置类,我们知道这些就足够了;
  2. AutoConfigurationImportSelector:承担自动配置的绝大部分逻辑,负责选择一些符合条件的自动配置类;
  3. metadata:标注在SpringBoot启动类上的@SpringBootApplication注解元数据

标【2】的this.group.selectImports的方法主要是针对前面的process方法处理后的自动配置类再进一步有选择的选择导入,将在4.2 有选择的导入自动配置类这小节深入分析。

4.1 分析自动配置的主要逻辑

这里继续深究前面 4 分析SpringBoot自动配置原理这节标【1】处的 this.group.process方法是如何处理自动配置相关逻辑的。

// AutoConfigurationImportSelector$AutoConfigurationGroup.java
// 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类
public void process(AnnotationMetadata annotationMetadata,
                    DeferredImportSelector deferredImportSelector) {
   
    Assert.state(
        deferredImportSelector instanceof AutoConfigurationImportSelector,
        () -> String.format("Only %s implementations are supported, got %s",
                            AutoConfigurationImportSelector.class.getSimpleName(),
                            deferredImportSelector.getClass().getName()));
    // 【1】,调用getAutoConfigurationEntry方法得到自动配置类放入autoConfigurationEntry对象中
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
                                   annotationMetadata);
    // 【2】,又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
    this.autoConfigurationEntries.add(autoConfigurationEntry); 
    // 【3】,遍历刚获取的自动配置类
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
   
        // 这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
        this.entries.putIfAbsent(importClassName, annotationMetadata); 
    }
}

上面代码中我们再来看标【1】的方法getAutoConfigurationEntry,这个方法主要是用来获取自动配置类有关,承担了自动配置的主要逻辑。直接上代码:

// AutoConfigurationImportSelector.java
// 获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
protected AutoConfigurationEntry getAutoConfigurationEntry(
    AutoConfigurationMetadata autoConfigurationMetadata,
    AnnotationMetadata annotationMetadata) {
   
    // 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
    if (!isEnabled(annotationMetadata)) {
   
        return EMPTY_ENTRY;
    }
    // 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据,
    // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
    // 将会获取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的注解数据
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 【1】得到spring.factories文件配置的所有自动配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata,
                                                             attributes);
    // 利用LinkedHashSet移除重复的配置类
    configurations = removeDuplicates(configurations);
    // 得到要排除的自动配置类,比如注解属性exclude的配置类
    // 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
    // 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
    checkExcludedClasses(
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值