SpringBoot源码解析(一)

news2024/11/9 6:08:14

SpringBoot自动装配原理

@SpringBootApplication注解

我们在使用SpringBoot时,通常使用的是@SpringBootApplication这个注解,比如:

而这个注解的定义为下图,可以发现这个注解上有另外三个注解:@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan,所以我们可以认为@SpringBootApplication是一个三合一注解;

所以我们也可以这么用,如果我们这么用就能自己控制要不要用@EnableAutoConfiguration这个注解,如果用就表示开启自动配置,如果不用就表示不开启自动配置,那开启和不开启自动配置到底该怎么理解呢?

SpringBoot的自动配置就是SpringBoot自动配置一些Bean,从而让开发人员在用SpringBoot时可以少去配置很多Bean,所以如果我们开启了自动配置,那最终Spring容器中就有SpringBoot帮我们配置的Bean,如果没有开启自动配置,那Spring容器中就没有这些Bean,就需要我们自己去配置。

@EnableAutoConfiguration

那我们来看看@EnableAutoConfiguration这个注解是如何工作的?先看注解源码定义:

其中非常核心的就是AutoConfigurationImportSelector,而AutoConfigurationImportSelector实现了DeferredImportSelector这个接口,Spring容器在启动时,会在解析完其他所有程序员定义的配置类之后,来调用AutoConfigurationImportSelector中的selectImports方法,然后把该方法返回的类名对应的类作为配置类进行解析。

该方法会利用SpringFactoriesLoader找到所有的META-INF/spring.factories文件中key为
EnableAutoConfiguration.class的value值,也就是众多自动配置类的类名。

拿到这些类名后会进行去重,去重完之后,就会看是否存在某些自动配置类需要排除,我们可以通过@EnableAutoConfiguration注解的exclude属性,或者spring.autoconfigure.exclude配置来指定一些自动配置类的名字,然后把它们从自动配置类集合中排除掉。

然后会继续利用ConfigurationClassFilter对自动配置类进行进一步筛选,ConfigurationClassFilter会利用AutoConfigurationMetadata进行筛选,而AutoConfigurationMetadata对象对应的是"METAINF/ spring-autoconfigure-metadata.properties"文件中的内容,这是一种加快SpringBoot启动速度的机制,默认是开启了的。不过要通过maven或gradle的方式引入springboot的依赖来使用才能看 到效果,因为这个文件的内容是在SpringBoot源码工程编译的时候自动生成出来的,当然我们也可以手动创建这个文件,以及这个文件的内容,自动生成的这个文件内容样例如下

内容格式为:自动配置类名.条件注解=条件

有了这个文件的内容,SpringBoot会在通过spring.facotries文件找到所有的自动配置类后,会把这个文件中的内容读出来,然后利用AutoConfigurationImportFilter对所有的自动配置类进行条件匹配,这里的条件判断,只会判断所需要的类是否存在。如果需要的类或者需要的Bean对应的类都不存在,那么肯定不符合条件了,对于像@ConditionalOnMissingBean这样的条件,在这一步是不会去判断的,最后条件匹配成功的自动配置类就会记录下来,并最终返回给Spring容器,继续进行其他条件的匹配。所以通过这个机制,使得Spring并不需要解析所有的自动配置类,从而提高了效率。

当然在这个过滤的过程中,如果日志级别等于trace级别,那么会把所有条件不匹配的自动配置类记录到日志中,如果日志框架配置了打印到控制台,那就会打印到控制台,比如:

在SpringBoot中,还有一个更加强大的统计自动配置类匹配结果的功能,就是可以配置
debug=true,只要开启了这个配置,那么Spring在解析每一个自动配置类时,就会将是否匹配的结
果进行记录,比如开启了debug=true,我们可以在控制台看到

可以看到这个匹配结果分别记录了:

1. 哪些自动配置类的条件是匹配的

2. 哪些自动配置类的条件是不匹配的,并且具体原因也会打印出来,比如是哪个类不存在

3. 哪些自动配置类是无条件的

这个功能的实现,是Spring解析具体的自动配置类上的各种条件注解的时候统计的,每解析一个条件注解,就会把结果记录在ConditionEvaluationReport对象中,当Spring容器启动完成后,会发布一个ContextRefreshedEvent事件,而SpringBoot提供了一个ConditionEvaluationReportLoggingListener会处理这个事件,接收到这个事件后就会把统计结果进
行打印。

自动配置类解析的大体流程为:

1. 读取spring.factories中的所有自动配置类

2. 看是否配置了需要排除的自动配置类,进行排除

3. 然后利用spring-autoconfigure-metadata.properties文件来过滤掉一些自动配置类(条件中指定的类不存在的自动配置类)

4. 解析过滤后自动配置类,判断自动配置类所有的条件注解,条件全部符合才会真正去解析自动配置类上的其他内 容,比如@Bean(也会进行条件判断)。

@SpringBootConfiguration

可以看到@SpringBootConfiguration就是对@Configuration类的简单包装,这个注解在之前的spring源码解析中已经解释的很清楚了。

@ComponentScan

componetScan注解想必也不陌生,在之前的spring源码解析中也已经解释的很清楚了。

条件注解原理

springboot中的条件注解

1. ConditionalOnBean:是否存在某个某类或某个名字的Bean
2. ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean
3. ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个
4. ConditionalOnClass:是否存在某个类
5. ConditionalOnMissingClass:是否缺失某个类
6. ConditionalOnExpression:指定的表达式返回的是true还是false
7. ConditionalOnWebApplication:当前应用是一个Web应用
8. ConditionalOnNotWebApplication:当前应用不是一个Web应用
9. ConditionalOnProperty:Environment中是否存在某个属性
10. ConditionalOnResource:指定的资源是否存在

当然我们也可以利用@Conditional来自定义条件注解。条件注解是可以写在类上和方法上的,如果某个条件注解写在了自动配置类上,那该自动配置类会不会生效就要看当前条件能不能符合,或者条件注解写在某个@Bean修饰的方法上,那这个Bean生不生效就看当前条件符不符合。

@Condional的原理和源码

从condition 的使用需求我们知道,这个是单条件满足的时候才实例化bean 和加入到spring 容器,而在spring中一个类的实例化必须要变成beanDefinition对象,而ConfigurationClassPostProcessor 是所有beanDefinition 对象的集散地,所有的beanDefinition 都会在这个类里面处理。那么我们要完成Condition 功能也必定在这个类里面,ConfigurationClassPostProcessor 类中的shouldSkip 方法就是做bean 过滤的。
 

我们可以发现,SpringBoot的自动配置,实际上就是SpringBoot的源码中预先写好了一些配置类,预先定义好了一些Bean,我们在用SpringBoot时,这些配置类就已经在我们项目的依赖中了,而这些自动配置类或自动配置Bean到底生不生效,就看具体所指定的条件了。

这个getMatchOutcome 是一个钩子方法,不同的注解调用的实现类不一样,这里看两个注解的实现

1、conditionalOnBean

Bean 存在时才掉用方法,这个其实很好理解,判断bean 是否存在其实就只要从BeanFactory 中找就行了,源码里面就是从BeanFactory 中找。

从BeanFactory 中获取对应的实例,如果有则匹配。

2、conditionalOnClass

当工程上下文中存在该类时才调用方法,实现原理就是通过Class.forName反射的方式,如果反射有异常则返回false,如果反射没异常返回true

Starter机制

Starter原理

那SpringBoot中的Starter和自动配置又有什么关系呢?

其实首先要明白一个Starter,就是一个Maven依赖,当我们在项目的pom.xml文件中添加某个Starter依赖时,其实就是简单的添加了很多其他的依赖,比如:

1、spring-boot-starter-web:引入了spring-boot-starter、spring-boot-starter-json、spring-boot-starter-tomcat等和Web开发相关的依赖包
2、spring-boot-starter-tomcat:引入了tomcat-embed-core、tomcat-embed-el、tomcat-embed-websocket等和Tomcat相关的依赖包

如果硬要把Starter机制和自动配置联系起来,那就是通过@ConditionalOnClass这个条件注解,因为这个条件注解的作用就是用来判断当前应用的依赖中是否存在某个类或某些类,比如:

上面代码中就用到了@ConditionalOnClass,用来判断项目中是否存在Servlet.class、
Tomcat.class、UpgradeProtocol.class这三个类,如果存在就满足当前条件,如果项目中引入了
spring-boot-starter-tomcat,那就有这三个类,然后机会将TomcateServletWebServerFactory假如到spring容器中,最终会调用getWebServer方法获取到web容器。

这个代码中就用到了@ConditionalOnMissingBean,意思是如果当前不存在 ServletWebServerFactory类型的Bean,那就符合条件,结合整体代码意思就是:如果用户自己没有定义ServletWebServerFactory类型的Bean,那代码中所定义的Bean就会生效;如果用户自己定义了ServletWebServerFactory类型的Bean,那代码中定义的Bean就不生效;所以这个注解是非常重要的,SpringBoot利用这个注解来决定到底用用户自己的Bean,还是用 SpringBoot自动配置的。

如果没有spring-boot-starter-tomcat那就可能没有这三个类(除非你自己单独引入了Tomcat相关的依赖)。所以这就做到了,如果我们在项目中要用Tomcat,那就依赖spring-boot-starter-web就够了,因为它默认依赖了spring-boot-starter-tomcat,从而依赖了Tomcat,从而Tomcat相关的Bean能生效。

而如果不想用Tomcat,那就得这么写:得把spring-boot-starter-tomcat给排除掉,再添加上spring-boot-starter-jetty的依赖,这样Tomcat的Bean就不会生效,Jetty的Bean就能生效,从而项目中用的就是Jetty。

自定义Starter

当公司里面需要把一些共用的api 封装成jar 包的时候,就可以尝试自定义启动器来做。自定义启动器用到的就是springboot中的SPI 原理,springboot 会去加载META-INF/spring.factories 配置文件,并加载EnableAutoConfiguration 为key的所有类。

利用这一点,我们定义一个工程也会有这个文件。
1、定义启动器核心工程,工程结构如下

spring.factories 配置内容

被springboot SPI 加载的类

这个RedisTemplate 实例就是我们封装的通用API,其他工程可以直接导入jar使用的。

2、自定义starter
我们还会定义一个没代码的工程,在这个工程里面没有任何代码,只有一个pom文件,Pom 里面就是对前面核心工程jar 包的导入,工程名定义为spring-boot-study-starter

3、自定义启动器使用
其实就只要在另外的springboot 工程pom 文件里面导入依赖就可以了,这个依赖就是自定义starter 那个工程的maven 坐标。

总结

SpringBoot启动时,最核心的也就是创建一个Spring容器,而创建Spring容器的过程中会注解做几件事情:

一、把SpringApplication.run(SpringBootExample.class)传入进来的MyApplication类做为配置类进行解析。
二、由于MyApplication类上定义了@SpringBootApplication,相当于定义了@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan注解。
三、所以SpringBoot会进一步解析这些注解@EnableAutoConfiguration,通过@import注解导入AutoConfigurationImportSelector这个配置类,因为它实现了DeferredImportSelector接口,所以Spring会在把其他配置类都解析完之后,在最后才解析AutoConfigurationImportSelector这个配置类(Spring Framework中的知识);

四、而AutoConfigurationImportSelector这个类的作用就是用来解析SpringBoot的自动配置类,那既然无法扫描到SpringBoot中的自动配置类,那怎么知道SpringBoot中有哪些自动配置类呢?默认情况下SpringBoot会提供一个spring.factories文件,并把所有自动配置类的名字记录在这个文件中,SPI 在springboot 中是去读取META-INF/spring.factories 目录的配置文件内容,把配置文件中的类加载到spring 容器中。如果你想把一个类加载到spring 容器中,也可以采用这种方式来做。把类配置到spring.factories 配置文件中即可。并且这件事也是发生在解析完用户的配置类之后的,那么我们总结一下,如果你想把一个类加载到spring 容器中管理有几种方式:

1、通过xml 的bean 标签;

2、通过加@Component 注解被@ComponentScan 扫描;

3、通过在spring.factories 配置该类前两者是加载本工程的bean,扫描本工程的bean,第三点可以加载第三方定义的jar 包中的bean,毕竟第三方jar 包的包名跟本工程包名可能不一样,所以前两个方式扫描不到。
四、@ComponentScan:扫描,扫描时会扫描到用户所定义的配置类,并解析用户的配置类,注意:扫描是扫描不到SpringBoot的自动配置的类,因为扫描的包路径不匹配,SpringBoot的包都是
org.springframework.boot.xxxx,用户都是自己的包路径。

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

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

相关文章

流类库与输入输出

来源:《C语言程序设计》 像C语言一样,C语言也没有输入输出语句。 但C标准库中有一个面向对象的输入输出软件包,即I/O流类库。 流是I/O流类的中心概念。 ------ I/O流类库是C语言中I/O函数在面向对象的程序设计方法中的一个替换产品。 -…

k8s图形化显示(KRM)

在master节点 kubectl get po -n kube-system 这个命令会列出 kube-system 命名空间中的所有 Pod 的状态和相关信息,比如名称、状态、重启次数等。 systemctl status kubelet #查看kubelet状态 yum install git #下载git命令 git clone https://gitee.com/duk…

理解鸿蒙app 开发中的 context

是什么 Context是应用中对象的上下文,其提供了应用的一些基础信息,例如resourceManager(资源管理)、applicationInfo(当前应用信息)、dir(应用文件路径)、area(文件分区…

ML1:sklearn env

sklearn: 中文参考: https://scikit-learn.cn/stable/modules/linear_model.html#ordinary-least-squares https://scikit-learn.org.cn/view/4.html ——》为主,不懂地方参考上面中文以及下面英文 英文参考: https://scikit…

五分钟入门双拼!

‍这是从零开始学双拼的第一篇:概述 双拼的原理 如果你使用全拼,想要完整敲出一个字的读音,需要敲出这个字拼音的每个字母。 虽然简拼能简化一点步骤,但除非是很常见的成语、俗语,否则重码率很高,选词很…

基于STM32的智能充电桩:集成RTOS、MQTT与SQLite的先进管理系统设计思路

一、项目概述 随着电动车的普及,充电桩作为关键基础设施,其智能化、网络化管理显得尤为重要。本项目旨在基于STM32微控制器开发一款智能充电桩,能够实现高效的充电监控与管理。项目通过物联网技术,提供实时数据监测、远程管理、用…

毕业后如何查找获取文献

当我们毕业后就无法再使用自己学校的数据库资源了,如果需要查找文献该从哪里获取资源呢?下面这个方法很简单而且有效: 一、首先选对科研工具 文献党下载器,把大量数据库资源整合在一起,直接去文献来源数据库查找获取…

串口接收,不定长数据接收

###1.CUBE-MX配置串口 2.我采用串口中断接收,打开中断接口 3.时钟同样8倍频,1分频,使用内部时钟 打开串口中断 main() { __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 启用空闲中断__HAL_UART_ENABLE_IT(&huart1, UART_IT_R…

2024 高质量 Java 面试题集锦:高级 Java 工程师面试八股汇总

最近感慨面试难的人越来越多了,一方面是市场环境,更重要的一方面是企业对 Java 的人才要求越来越高了。 基本上这样感慨的分为两类人,第一,虽然挂着 3、5 年经验,但肚子里货少,也没啥拿得出手的项目&#x…

express 使用JWT认证

1、JWT的理解 JWT 的组成部分: 分别是 Header(头部)、Payload(有效荷载)、Signature(签名) 三者之间使用英文的"."分隔, Pyload 部分才是真正的用户信息,他是用户信息经过加密之后生成的字符串 Header 和 Signature 是 安全性相关的部分,只是为了保证 Tok…

linux将mysql加到systemctl命令中

linux中,想将mysql加到systemctl命令中,首先需要确定mysql的安装位置 在/etc/systemd/system目录下新建mysql.service vim /etc/systemd/system/mysql.service 复制如下内容:确保你自己的mysql路径是否正确 [Unit] DescriptionMySQL Server…

【Mac】安装 VMware Fusion Pro

VMware Fusion Pro 软件已经正式免费提供给个人用户使用! 1、下载 【官网】 下拉找到 VMware Fusion Pro Download 登陆账号 如果没有账号,点击右上角 LOGIN ,选择 REGISTER 注册信息除了邮箱外可随意填写 登陆时,Username为…

【VR】PICO 手部追踪 steamvr内无法识别,依旧识别手柄的解决方案

一、问题描述 && 原因分析 1.PICO4 手部追踪 steamvr内无法识别,依旧识别手柄的解决方案 尽管平放(或关闭手柄连接)之后,在 PICO 一体机中进入了手部追踪状态, 但只要进入 steamvr,就无法正确识别…

【LeetCode:64】最小路径和(Java)

题目链接 64. 最小路径和 题目描述 给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 说明:每次只能向下或者向右移动一步。 示例 1: 输入:grid [[1,…

VUE项目是如何启动的

当我们执行npm run serve,vue就会启动到这个界面&#xff0c;这个流程是怎么的 下典型的 Vue CLI 项目结构&#xff1a; public/index.html 这是项目的主 HTML 文件&#xff0c;Vue 应用会被挂载到这个文件中的 <div id"app"></div> 元素上。 <!DO…

LeetCode100之旋转图像(48)--Java

1.问题描述 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 示例1 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&…

吴恩达深度学习笔记:序列模型(Sequence Models) 1.1-1.2

目录 第五门课 序列模型(Sequence Models)第一周 循环序列模型&#xff08;Recurrent Neural Networks&#xff09;1.1 为什么选择序列模型&#xff1f;&#xff08;Why Sequence Models?&#xff09;1.2 数学符号&#xff08;Notation&#xff09; 第五门课 序列模型(Sequenc…

安装和运行开发微信小程序

下载HBuilder uniapp官网 uni-app官网 微信开发者工具 安装 微信小程序 微信小程序 官网 微信小程序 配置 运行 注意&#xff1a;运行前需要开启服务端口 如果运行看不到效果&#xff0c;设置下基础库选别的版本 配置

Java反射、注解、泛型——针对实习面试

目录 Java反射、注解、泛型什么是反射&#xff1f;反射有什么优缺点&#xff1f;优点缺点 什么是泛型?泛型的优点泛型的实现 泛型怎么使用&#xff1f;泛型类泛型方法泛型接口类型参数命名约定泛型的类型限定泛型的通配符 什么是泛型擦除机制&#xff1f;为什么要擦除&#xf…

【SpringMVC】——Cookie和Session机制

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;实践 1&#xff1a;获取URL中的参数 &#xff08;1&#xff09;PathVariable 2&…