乐趣区

关于spring:Spring-Boot-启动注解分析

@[toc]
尽管咱们在日常开发中,Spring Boot 应用十分多,算是目前 Java 开发畛域一个标配了,然而小伙伴们认真想想本人的面试经验,和 Spring Boot 相干的面试题都有哪些?个人感觉应该是比拟少的,Spring Boot 实质上还是已经 SSM 那一套,只是通过各种 starter 简化了配置而已,其余都是截然不同的,所以 Spring Boot 中很多面试题还是得回归到 Spring 中去解答!当然这并不是说 Spring Boot 中没什么可问的,Spring Boot 中其实也有一个十分经典的面试题,那就是 Spring Boot 中的自动化配置是怎么实现的?明天松哥就来和各位小伙伴聊一下这个问题。

其实松哥之前和小伙伴们聊过相干的问题,不过都是零散的,没有零碎梳理过,之前也率领小伙伴们自定义过一个 starter,置信各位小伙伴对于 starter 的原理也有肯定理解,所以明天这篇文章一些过于细节的内容我就不赘述了,大家能够翻看之前的文章。

1. @SpringBootApplication

要说 Spring Boot 的自动化配置,那必须从我的项目的启动类 @SpringBootApplication 说起,这是整个 Spring Boot 宇宙的终点,咱们先来看下这个注解:

@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 注解组合了多个常见注解的性能,其中:

  • 前四个是元注解,这里咱们不做探讨。
  • 第五个 @SpringBootConfiguration 是一个反对配置类的注解,这里咱们也不做探讨。
  • 第六个 @EnableAutoConfiguration 这个注解就示意开启自动化配置,这是咱们明天要聊得重点。
  • 第七个 @ComponentScan 是一个包扫描注解,为什么 Spring Boot 我的项目中的 Bean 只有放对地位就会被主动扫描到,和这个注解无关。

别看这里注解多,其实真正由 Spring Boot 提供的注解一共就两个,别离是 @SpringBootConfiguration@EnableAutoConfiguration 两个,其余注解在 Spring Boot 呈现之前就曾经存在多年了。

2. @EnableAutoConfiguration

接下来咱们来看看 @EnableAutoConfiguration 是如何实现自动化配置的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

这个注解起关键作用的就是两个货色:

  1. @AutoConfigurationPackage:这个示意主动扫描各种第三方的注解,在之前的文章中松哥曾经和大家聊过这个注解的作用了,传送门:@AutoConfigurationPackage 和 @ComponentScan 有何区别?
  2. @Import 则是在导入 AutoConfigurationImportSelector 配置类,这个配置类里边就是去加载各种自动化配置类的。

3. AutoConfigurationImportSelector

AutoConfigurationImportSelector 类中的办法比拟多,入口的中央则是 process 办法,所以咱们这里就从 process 办法开始看起:

@Override
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()));
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(annotationMetadata);
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}

从类名就可以看进去,跟自动化配置相干的对象是由 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata); 进行加载的。

当然这里的 getAutoConfigurationEntry 办法实际上就是以后类提供的办法,咱们来看下该办法:

protected AutoConfigurationEntry getAutoConfigurationEntry(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 = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

这里源码的办法命名都做的不错,基本上都能做到见名知意,小伙伴们日常开发中,应该向这样的命名思路看齐。接下来咱们就来挨个看一下这里的要害办法。

3.1 isEnabled

首先调用 isEnabled 办法去判断自动化配置到底有没有开启,这个次要是因为咱们及时在我的项目中引入了 spring-boot-starter-xxx 之后,咱们也能够通过在 application.properties 中配置 spring.boot.enableautoconfiguration=false 来敞开所有的自动化配置。

相干源码如下:

protected boolean isEnabled(AnnotationMetadata metadata) {if (getClass() == AutoConfigurationImportSelector.class) {return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
    }
    return true;
}

3.2 getCandidateConfigurations

接下来调用 getCandidateConfigurations 办法去获取所有候选的自动化配置类,这些候选的自动化配置类次要来自两个中央:

  1. 在之前的自定义 starter 中松哥和大家聊过,咱们须要在 claspath\:META-INF/spring.factories 中定义进去所有的自动化配置类,这是起源一。
  2. Spring Boot 自带的自动化配置类,这个在之前的 vhr 视频中也和小伙伴们屡次讲过,Spring Boot 自带的自动化配置类位于 spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中。

相干源码如下:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = new ArrayList<>(SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
    ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
    Assert.notEmpty(configurations,
            "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you"
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

这里加载到的自动化配置类的全门路被存入到 configurations 对象中,该对象有两个获取的中央:

  1. 调用 SpringFactoriesLoader.loadFactoryNames 办法获取,这个办法细节我就不带大家看了,比较简单,实质上就是去加载 META-INF/spring.factories 文件,这个文件中定义了大量的自动化配置类的全门路。
  2. 调用 ImportCandidates.load 办法去加载,这个就是加载 spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中的自动化配置类。

如果这两个中央都没有加载到任何自动化配置类,那么就会抛出一个异样。

3.3 removeDuplicates

removeDuplicates 办法示意移除候选自动化配置类中反复的类,移除的思路也很有意思,就用一个 LinkedHashSet 直达一下就行了,源码如下:

protected final <T> List<T> removeDuplicates(List<T> list) {return new ArrayList<>(new LinkedHashSet<>(list));
}

能够看到这些源码里有时候一些解决思路也很有意思。

3.4 getExclusions

getExclusions 办法示意须要获取到所有被排除的自动化配置类,这些被排除的自动化配置类能够从三个中央获取:

  1. 以后注解的 exclude 属性。
  2. 以后注解的 excludeName 属性。
  3. application.properties 配置文件中的 spring.autoconfigure.exclude 属性。

来看一下相干源码:

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {Set<String> excluded = new LinkedHashSet<>();
    excluded.addAll(asList(attributes, "exclude"));
    excluded.addAll(asList(attributes, "excludeName"));
    excluded.addAll(getExcludeAutoConfigurationsProperty());
    return excluded;
}

跟下面解说的三点刚好对应。

3.5 checkExcludedClasses

这个办法是查看所有被排除的自动化配置类,因为 Spring Boot 中的自动化配置类能够自定义,并不需要对立实现某一个接口或者对立继承某一个类,所以在写排除类的时候,如果写错了编译是校验不进去的,像上面这种:

@SpringBootApplication(exclude = HelloController.class)
public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);
    }
}

因为 HelloController 并不是一个自动化配置类,所以这样写我的项目启动的时候就会报错,如下:

这个异样从哪来的呢?其实就是来自 checkExcludedClasses 办法,咱们来看下该办法:

private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {List<String> invalidExcludes = new ArrayList<>(exclusions.size());
    for (String exclusion : exclusions) {if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {invalidExcludes.add(exclusion);
        }
    }
    if (!invalidExcludes.isEmpty()) {handleInvalidExcludes(invalidExcludes);
    }
}
protected void handleInvalidExcludes(List<String> invalidExcludes) {StringBuilder message = new StringBuilder();
    for (String exclude : invalidExcludes) {message.append("\t-").append(exclude).append(String.format("%n"));
    }
    throw new IllegalStateException(String.format(
            "The following classes could not be excluded because they are not auto-configuration classes:%n%s",
            message));
}

能够看到,在 checkExcludedClasses 办法中,会首先找到所有位于以后类门路下然而却不蕴含在 configurations 中的所有被排除的自动化配置类,因为 configurations 中的就是所有的自动化配置类了,所以这些不存在于 configurations 中的类都是有问题的,都不是自动化配置类,将这些有问题的类收集起来,存入到 invalidExcludes 变量中,而后再进行额定的解决。

所谓额定的解决就是在 handleInvalidExcludes 办法中抛出异样,后面截图中的异样就是来自这里。

3.6 removeAll

这个办法就一个工作,就是从 configurations 中移除掉那些被排除的自动化配置类。configurations 自身就是 List 汇合,exclusions 则是一个 Set 汇合,所以这里间接移除即可。

3.7 filter

当初咱们曾经加载了所有的自动化配置类了,然而这些配置类并不是都会失效,具体是否失效,还要看你的我的项目是否应用了具体的依赖。

例如,当初加载的自动化配置里里边就蕴含了 RedisAutoConfiguration,这个是主动配置 Redis 的,然而因为我的我的项目中并没有应用 Redis,所以这个自动化配置类并不会失效。这个过程就是由 getConfigurationClassFilter().filter(configurations); 来实现的。

先说一个准备常识:

因为咱们我的项目中的自动化配置类特地多,每一个自动化配置类都会依赖别的类,当别的类存在时,这个自动化配置类才会失效,这一堆相互之间的依赖关系,存在于 spring-boot-autoconfigure-3.0.6.jar!/META-INF/spring-autoconfigure-metadata.properties 文件之中,我轻易举一个该文件中的配置:

  • org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.annotation.EnableRabbit 示意 RabbitAnnotationDrivenConfiguration 类要失效有一个必备条件就是以后我的项目类门路下要存在 org.springframework.amqp.rabbit.annotation.EnableRabbit

咱们来看看 RabbitAnnotationDrivenConfiguration 类的注解:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EnableRabbit.class)
class RabbitAnnotationDrivenConfiguration {}

这个类和配置文件中的内容统一。

这个准备常识搞懂了,接下来的内容就好了解了。

先来看 getConfigurationClassFilter 办法,这个就是获取所有的过滤器,如下:

private ConfigurationClassFilter getConfigurationClassFilter() {if (this.configurationClassFilter == null) {List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
        for (AutoConfigurationImportFilter filter : filters) {invokeAwareMethods(filter);
        }
        this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
    }
    return this.configurationClassFilter;
}

能够看到,这里获取到的过滤器都是 AutoConfigurationImportFilter 类型的,这个类型的过滤器只有三个实例,如下图:

从这三个实例的名字中,基本上就能看进去各自的作用:

  • OnClassCondition:这个就是条件注解 @ConditionalOnClass 的断定条件,看名字就晓得用来判断以后 classpath 下是否存在某个类。
  • OnWebApplicationCondition:这个是条件注解 ConditionalOnWebApplication 的断定条件,用来判断以后零碎环境是否是一个 Web 环境。
  • OnBeanCondition:这个是条件注解 @ConditionalOnBean 的断定条件,就是判断以后零碎下是否存在某个 Bean。

这里获取到的三个 AutoConfigurationImportFilter 过滤器其实就是下面这三个。接下来执行 filter 办法,如下:

List<String> filter(List<String> configurations) {long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean skipped = false;
    for (AutoConfigurationImportFilter filter : this.filters) {boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
        for (int i = 0; i < match.length; i++) {if (!match[i]) {candidates[i] = null;
                skipped = true;
            }
        }
    }
    if (!skipped) {return configurations;}
    List<String> result = new ArrayList<>(candidates.length);
    for (String candidate : candidates) {if (candidate != null) {result.add(candidate);
        }
    }
    return result;
}

这里就是遍历这三个过滤器,而后别离调用各自的 match 办法和 144 个自动化配置类进行匹配,如果这些自动化配置类所须要的条件失去满足,则 match 数组对应的地位就为 true,否则就为 false。

而后遍历 match 数组,将不满足条件的自动化配置类置为 null,最初再把这些 null 移除掉。

这样就获取到了咱们须要进行自动化配置的类了。

最初一句 fireAutoConfigurationImportEvents 则是触发自动化配置类导入事件,这个没啥好说的~

当这些自动化配置类加载进来之后,接下来就是各种条件注解来决定这些配置类是否失效了,这些都比较简单了,之前在 vhr 种也和小伙伴们讲过屡次了,这里就不再啰嗦了~

好啦,通过下面的梳理置信小伙伴们对 Spring Boot 自动化配置类的加载有一个大略的认知了吧~

退出移动版