Spring Boot 启动注解分析

这篇具有很好参考价值的文章主要介绍了Spring Boot 启动注解分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

虽然我们在日常开发中,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 并不是一个自动化配置类,所以这样写项目启动的时候就会报错,如下:

Spring Boot 启动注解分析,spring boot,spring boot,java,数据库

这个异常从哪来的呢?其实就是来自 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 类型的,这个类型的过滤器只有三个实例,如下图:

Spring Boot 启动注解分析,spring boot,spring boot,java,数据库

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

  • 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 自动化配置类的加载有一个大概的认知了吧~文章来源地址https://www.toymoban.com/news/detail-641336.html

到了这里,关于Spring Boot 启动注解分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • spring boot启动原理分析

    spring boot启动原理分析

    springboot启动类中有两个关键的地方 1.@SpringBootApplication 注解 2.SpringApplication.run 方法 SpringBootApplication 注解分析 SpringBootApplication 注解由三大注解构成, SpringBootConfiguration 、 EnableAutoConfiguration 、 ComponentScan 。 SpringBootConfiguration 中其实主要就是包装了Configuration注解 EnableAutoConf

    2024年02月11日
    浏览(9)
  • java spring boot 注解、接口和问题解决方法(持续更新)

    java spring boot 注解、接口和问题解决方法(持续更新)

    @RestController         是SpringMVC框架中的一个注解,它结合了@Controller和@ResponseBody两个注解的功能,用于标记一个类或者方法,表示该类或方法用于处理HTTP请求,并将响应的结果直接返回给客户端,而不需要进行视图渲染 @Controller         是Spring Framework中的注解,用于

    2024年02月06日
    浏览(11)
  • Spring Boot实现第一次启动时自动初始化数据库

    Spring Boot实现第一次启动时自动初始化数据库

    在现在的后端开发中,只要是运用联系型数据库,信任SSM架构(Spring Boot + MyBatis)已经成为首选。 不过在咱们第一次运转或许布置项目的时分,一般要先手动衔接数据库,履行一个SQL文件以创立数据库以及数据库表格完结 数据库的初始化作业 ,这样咱们的SSM应用程序才能够

    2024年02月03日
    浏览(13)
  • spring boot 启动报错---java: 无法访问org.springframework.boot.SpringApplication 错误的类文件:

    spring boot 启动报错---java: 无法访问org.springframework.boot.SpringApplication 错误的类文件:

    目录 错误提示信息: 原因: 解决办法: 具体步骤: 主要是因为 spring boot 3.0发布了 ,在创建项目时,默认为3.0 ` 但同时, spring boot 3.0 只支持jdk 17 ,在平时创建时,都喜欢使用jdk8 由于 spring boot 版本默认3.0,同时jdk选择的是 8 ,就会导致这个错误 将 spring boot 版本和jdk统一

    2024年02月15日
    浏览(10)
  • Spring boot 常见注解

    Spring Boot是一个基于Spring框架的快速开发框架,它通过自动化配置和约定优于配置的原则,简化了Spring应用程序的开发过程。Spring Boot可以帮助开发者快速构建独立的、生产级别的应用程序,并且可以与其他Spring框架和第三方库无缝集成。 Spring Boot提供了很多便利的特性,比如

    2024年01月18日
    浏览(10)
  • Spring boot注解讲解

    Spring boot注解讲解

    人不走空                                                                            目录         🌈个人主页:人不走空       💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨   注解 注解列表如下 JPA注解 作者其他作品:   @SpringBootApplication :申

    2024年02月19日
    浏览(13)
  • Spring Boot常用注解

    在 Spring Boot 中,有许多注解用于简化和标识应用程序的不同方面。以下是一些常用的 Spring Boot 注解: @SpringBootApplication : 用于标识主应用程序类。通常与 @EnableAutoConfiguration 、 @ComponentScan 和 @Configuration 一起使用,它是一个复合注解,用于简化配置。 @Controller : 用于标识控

    2024年01月19日
    浏览(9)
  • Java(一):创建 Spring Boot 项目并实现连接操作MySQL数据库

    Java(一):创建 Spring Boot 项目并实现连接操作MySQL数据库

    MySQL 命令 Maven 相关地址 下载地址: https://maven.apache.org/ maven配置方法地址: https://developer.aliyun.com/mvn/guide 仓库搜索地址: https://mvnrepository.com/ https://repo.maven.apache.org/ maven 本地配置 conf/settings.xml 下载 idea 并配置本地环境 maven Maven 构建 生命周期 Maven 的构建 生命周期 包括 三

    2024年02月07日
    浏览(18)
  • Spring boot自定义注解

    定义一个注解类 使用AOP对注解进行解析,需要定义一个切面类,包括自定义的切点方法normalPointCut(),以及连接点的处理方法normalPointAround()。连接点中的ProceedingJoinPoint可以获取被代理类的方法属性等。 2.1 定义注解 2.2 实现参数解释器 记得实现WebMvcConfigurer 接口配置LimitReque

    2023年04月27日
    浏览(12)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包