当前位置: 首页 > 滚动 > 内容页

当前资讯!Spring Boot 启动注解分析

来源:清一色财经 2023-06-02 17:43:33

个人感觉应该是比较少的,Spring Boot 本质上还是曾经 SSM 那一套,只是通过各种 starter 简化了配置而已,其他都是一模一样的,所以 Spring Boot 中很多面试题还是得回归到 Spring 中去解答!当然这并不是说 Spring Boot 中没什么可问的,Spring Boot 中其实也有一个非常经典的面试题,那就是 Spring Boot 中的自动化配置是怎么实现的?

虽然我们在日常开发中,SpringBoot使用非常多,算是目前Java开发领域一个标配了,但是小伙伴们仔细想想自己的面试经历,和SpringBoot相关的面试题都有哪些?个人感觉应该是比较少的,SpringBoot本质上还是曾经SSM那一套,只是通过各种starter简化了配置而已,其他都是一模一样的,所以SpringBoot中很多面试题还是得回归到Spring中去解答!当然这并不是说SpringBoot中没什么可问的,SpringBoot中其实也有一个非常经典的面试题,那就是SpringBoot中的自动化配置是怎么实现的?今天松哥就来和各位小伙伴聊一下这个问题。


【资料图】

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

1. @SpringBootApplication

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

@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是一个包扫描注解,为什么SpringBoot项目中的Bean只要放对位置就会被自动扫描到,和这个注解有关。

别看这里注解多,其实真正由SpringBoot提供的注解一共就两个,分别是@SpringBootConfiguration和@EnableAutoConfiguration两个,其他注解在SpringBoot出现之前就已经存在多年了。

2. @EnableAutoConfiguration

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

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

这个注解起关键作用的就是两个东西:

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

3. AutoConfigurationImportSelector

AutoConfigurationImportSelector类中的方法比较多,入口的地方则是process方法,所以我们这里就从process方法开始看起:

@Overridepublic 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); }}

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

当然这里的getAutoConfigurationEntry方法实际上就是当前类提供的方法,我们来看下该方法:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) {  return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set 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方法去获取所有候选的自动化配置类,这些候选的自动化配置类主要来自两个地方:

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

相关源码如下:

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List 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对象中,该对象有两个获取的地方:

调用SpringFactoriesLoader.loadFactoryNames方法获取,这个方法细节我就不带大家看了,比较简单,本质上就是去加载META-INF/spring.factories文件,这个文件中定义了大量的自动化配置类的全路径。调用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  List removeDuplicates(List list) { return new ArrayList<>(new LinkedHashSet<>(list));}

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

3.4 getExclusions

getExclusions方法表示需要获取到所有被排除的自动化配置类,这些被排除的自动化配置类可以从三个地方获取:

当前注解的exclude属性。当前注解的excludeName属性。application.properties配置文件中的spring.autoconfigure.exclude属性。

来看一下相关源码:

protected Set getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) { Set 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 configurations, Set exclusions) { List 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 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.Cnotallow=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 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 filter(List 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 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 种也和小伙伴们讲过多次了,这里就不再啰嗦了~

关键词:
x 广告
x 广告