Spring Boot 启动注解分析

news2025/1/4 19:47:25

文章目录

    • 1.  @SpringBootApplication
    • 2. @EnableAutoConfiguration
    • 3. AutoConfigurationImportSelector
      • 3.1 isEnabled
      • 3.2 getCandidateConfigurations
      • 3.3 removeDuplicates
      • 3.4 getExclusions
      • 3.5 checkExcludedClasses
      • 3.6 removeAll
      • 3.7 filter

虽然我们在日常开发中,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 自动化配置类的加载有一个大概的认知了吧~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/602707.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Golang】golang中http请求的context传递到异步任务的坑

文章目录 前言一、HTTP请求的Context传递到异步任务的坑 前言 在golang中&#xff0c;context.Context可以用来用来设置截止日期、同步信号&#xff0c;传递请求相关值的结构体。 与 goroutine 有比较密切的关系。 在web程序中&#xff0c;每个Request都需要开启一个goroutin…

使用docker部署nginx并支持https

配置nginx支持https&#xff0c;其实也简单&#xff0c;搞个证书&#xff0c;然后修改下配置文件就好了。我以前一篇文章&#xff08;使用docker部署多个nginx站点并配置负载均衡&#xff09;为例&#xff0c;做个记录。 如前所述&#xff0c;我使用docker&#xff0c;部署了3…

一文带你看懂软件测试(功能、接口、性能、自动化)详解

全文2000字&#xff0c;预计阅读时间10分钟&#xff0c;建议先点赞收藏慢慢看 一、软件测试功能测试 测试用例编写是软件测试的基本技能&#xff1b;也有很多人认为测试用例是软件测试的核心&#xff1b;软件测试中最重要的是设计和生成有效的测试用例&#xff1b;测试用例是测…

面了个京东拿30k出来的,牛逼到家了。。。

今天上班开早会就是新人见面仪式&#xff0c;听说来了个很厉害的大佬&#xff0c;年纪还不大&#xff0c;是上家公司离职过来的&#xff0c;薪资已经达到中高等水平&#xff0c;很多人都好奇不已&#xff0c;能拿到这个薪资应该人不简单&#xff0c;果然&#xff0c;自我介绍的…

RK平台如何配置USB功能

简介 RK平台基本能够通过dts配置就能实现USB功能。为了方便理解&#xff0c;我这里分三部分来介绍&#xff0c;包括&#xff1a;usb-phy&#xff0c;usb控制器&#xff0c;usb供电。 usb-phy usb-phy负责最底层的信号转换&#xff0c;主要是硬件的差分信号转换成数字信号传给…

十条ChatGPT常用的Prompt

Prompt 本文数据来源&#xff1a;Will 3.6-6.16 硅谷&#xff0c;原作者&#xff1a;rowancheung 一&#xff0c;简化复杂的信息 Prompt&#xff1a; 将&#xff08;主题&#xff09;分解成更小、更容易理解的部分。使用类比和现实生活中的例子来简化概念并使其更相关 Brea…

Python之并发多线程操作

一、threading模块介绍 multiprocess模块的完全模仿了threading模块的接口&#xff0c;二者在使用层面&#xff0c;有很大的相似性 二、开启线程的两种方式 方式一 #方式一 from threading import Thread import time def sayhi(name):time.sleep(2)print(%s say hello %na…

最大公约数(GCD) 与 最小公倍数(LCM)的 定义、关系、求法

最大公约数 与 最小公倍数 约数 和 倍数最大公约数最小公倍数 最大公约数与最小公倍数的关系求最大公约数、最小公倍数例一例二 约数 和 倍数 如果数 a a a能被数 b b b整除&#xff0c; a a a就叫做 b b b的倍数&#xff0c; b b b就叫做 a a a的约数。 约数和倍数都表示一个…

从0-1实战react项目

文章目录 1. 安装2. 完成一个组件开发3. 添加路由3. 引入element-react1. 运行发现报错./node_modules/element-react/dist/npm/es5/src/locale/format.js2. 接着又报错The <Router /> component appears to be a function component that returns a class instance. Cha…

[SpringBoot]关于Profile配置文件关于Slf4j日志

关于Profile配置文件 在Spring系列框架中&#xff0c;关于配置文件&#xff0c;允许同时存在多个配置文件&#xff08;例如同时存在a.yml、b.yml等&#xff09;&#xff0c;并且&#xff0c;你可以按需切换某个配置文件&#xff0c;这些默认不生效、需要被激活才生效的配置&am…

【ProtoBuf】protobuf序列化协议

Protobuf介绍 Protobuf (Protocol Buffers) 是谷歌开发的一款无关平台&#xff0c;无关语言&#xff0c;可扩展&#xff0c;轻量级高效的序列化结构的数据格式&#xff0c;用于将自定义数据结构序列化成字节流&#xff0c;和将字节流反序列化为数据结构。所以很适合做数据存储…

容器底层实现技术

一、Namespace 和 Cgroup 1、容器技术发展历史 2、Docker 容器实现原理 1. Docker 容器在实现上通过 namespace 技术实现进程隔离&#xff0c; 通过Cgroup 技术实现容器进程可用资源的限制 3、Namespace Namespace &#xff1a;命名空间 1. 作用&#xff1a;资源隔离 2. 原理&…

web前端课程作业设计:个人简历

一.说明 今天博主的web前端选修课结课了&#xff0c;期末大作业也提交了&#xff0c;今天写一篇博客把我的大作业分享给大家。 二.题目 1. 大作业题目 个人简历主页设计 2. 内容要求 应尽量包含以下内容&#xff1a; 包含个人基本信息、教育背景、个人风采、与我联系四块…

企业四要素核验-企业四要素核验接口-api接口

接口地址&#xff1a; https://登录后显示/pyi/184/358(支持:http/https)) 在线查询&#xff1a;https://www.wapi.cn/api_detail/184/358.html 网站地址&#xff1a;https://www.wapi.cn 返回格式&#xff1a;json,xml 请求方式&#xff1a;GET,POST 请求说明&#xff1a; …

【实用篇】Elasticsearch01

分布式搜索引擎01 – elasticsearch基础 1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&#xff1a; 在GitHub搜索…

智慧公厕系统如何通过物联网技术提高公厕的管理效率

智慧公厕系统可以通过物联网技术&#xff0c;实现公共卫生间的智能化管理和服务&#xff0c;提高管理效率。本文将详细介绍智慧公厕系统如何通过物联网技术提高公共卫生间的管理效率&#xff0c;从硬件、软件、系统等方面逐一分析。 XP-智慧厕所方案-HYF20230328&#xff08;16…

工业企业为什么要用边缘计算网关?

在我们进入智能制造和工业4.0的新时代&#xff0c;工业企业的数据需求正急速增长。传感器&#xff0c;机器和设备每分钟都在产生大量数据&#xff0c;它们对实时处理和分析的需求比以往任何时候都要强烈。这就是为什么工业企业需要边缘计算网关。 边缘计算网关在物联网架构中担…

大数据:Apache hive分布式sql计算平台,hive架构,hive部署,hive初体验

大数据&#xff1a;Apache hive分布式sql计算平台 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle&a…

Java002——JDK的安装以及配置环境变量

为什么要安装jdk 1、JDK 全称 Java Development Kit&#xff0c;意为 Java 开发工具。&#xff0c;要想开发java程序就必须安装JDK。没有JDK的话&#xff0c;无法编译运行Java程序。 2、JDK包含的基本组件包括以下文件&#xff1a;   javac.exe,用于编译java文件&#xff0c…

不经意传输(OT)了解

概述 OT&#xff0c;不经意传输&#xff0c;常被大量用于安全多方计算中&#xff0c;能够很大程度决定一个SMPC协议的效率。它的核心概念是接收方可以从发送方&#xff08;持有秘密信息&#xff09;手中选择性接收自己想要的信息而接收方对所选择的信息内容一无所知。目前有2-…