SpringBoot3 基础教程之九核心原理
大纲
- SpringBoot3 基础教程之一快速入门
- SpringBoot3 基础教程之二常规配置
- SpringBoot3 基础教程之三 Web 开发
- SpringBoot3 基础教程之四 Web 开发
- SpringBoot3 基础教程之五基础特性
- SpringBoot3 基础教程之六场景整合
- SpringBoot3 基础教程之七场景整合
- SpringBoot3 基础教程之九核心原理
依赖管理机制
为什么导入 spring-boot-starter-web 后,所有相关的依赖都导入进来了?
- 开发什么场景,导入什么场景启动器
- Maven 依赖的传递原则。A -> B -> C,那么 A 就拥有 B 和 C
- 导入场景启动器后,会自动把这个场景的所有核心依赖全部导入进来
为什么版本号都不用写?
- 每个 SpringBoot 项目都有一个父项目
spring-boot-starter-parent
parent
的父项目是spring-boot-dependencies
- 父项目是版本仲裁中心,会将所有常见 Jar 包的依赖版本都声明好了,比如
mysql-connector-j
如何自定义依赖的版本号?
- 第一种方式:直接在当前 Maven 配置文件的
<properties></properties>
标签中声明父项目用的版本属性的 Key - 第二种方式:直接在导入依赖的时候声明版本
- 上述两种方式都是利用 Maven 的就近原则特性
如何导入第三方的 Jar 包
- 对于
spring-boot-starter-parent
没有管理的 Jar 包依赖,直接在 Maven 的配置文件中自行声明就可以
自动配置原理
SpringBoot 应用关注的三大核心:场景
、配置
、组件
。
自动配置流程
初步理解
- 自动配置 SpringMVC、Tomcat 等
- 导入场景,容器中就会自动配置好这个场景的核心组件
- 以前:DispatcherServlet、ViewResolver、CharacterEncodingFilter ….
- 现在:自动配置好的这些组件
- 验证:容器中有了什么组件,就具有什么功能
1 |
|
默认的包扫描规则
@SpringBootApplication
注解标注的类就是主程序类- SpringBoot 只会扫描主程序类所在的包及其下面的子包,即自动的
component-scan
功能 - 自定义包的扫描路径
- 第一种方式:
@SpringBootApplication(scanBasePackages = "com.clay")
- 第二种方式:
@ComponentScan("com.clay")
直接指定扫描的包路径
- 第一种方式:
配置默认值
- 配置文件的所有配置项是和某个类的对象值进行一一绑定的
- 绑定了配置文件中每一项值的类,称为配置属性类
- 比如:
ServerProperties
绑定了所有 Tomcat 服务器有关的配置MultipartProperties
绑定了所有文件上传相关的配置- ….. 参照 官方文档 或者参照绑定的配置属性类
按需加载自动配置
- 导入场景启动器
spring-boot-starter-web
- 场景启动器除了会导入相关功能的依赖,还会导入
spring-boot-starter
,它是所有starter
的starter
,是基础核心starter
spring-boot-starter
导入了一个包spring-boot-autoconfigure
,里面都是各种场景的AutoConfiguration
自动配置类- 虽然全场景的自动配置都在
spring-boot-autoconfigure
这个包中,但并不是全部都默认开启的,导入哪个场景启动器才会开启哪个场景的自动配置
- 导入场景启动器
总结
导入场景启动器会触发 spring-boot-autoconfigure
这个包的自动配置生效,Spring 的 IOC 容器中就会具有相关场景的功能。
进阶理解
思考以下问题
- 1、SpringBoot 是怎么实现导一个
starter
,写一些简单配置,开发者无需关心整合,应用就能跑起来的? - 2、为什么 Tomcat 的端口号可以配置在
application.properties
中,并且 Tomcat 能启动成功? - 3、导入场景启动器后,哪些自动配置能生效?
导入 Web 开发场景
spring-boot-starter-web
- 1、场景启动器导入了相关场景的所有依赖,如
spring-boot-starter-json
、spring-boot-starter-tomcat
、spring-webmvc
。 - 2、每个场景启动器都引入了一个
spring-boot-starter
,即核心场景启动器。 - 3、核心场景启动器引入了
spring-boot-autoconfigure
包。 - 4、
spring-boot-autoconfigure
里面囊括了所有场景的自动配置。 - 5、只要
spring-boot-autoconfigure
这个包下的所有类都能生效,那么相当于 SpringBoot 官方写好的整合功能就生效了。 - 6、SpringBoot 默认是扫描不到
spring-boot-autoconfigure
下写好的所有配置类(这些配置类给我们做了整合操作),默认只扫描主程序类所在的包及其下面的子包。
- 1、场景启动器导入了相关场景的所有依赖,如
主程序注解
@SpringBootApplication
- 1、
@SpringBootApplication
由三个注解组成,分别是@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
。 - 2、SpringBoot 默认只能扫描自己主程序所在的包及其下面的子包,扫描不到
spring-boot-autoconfigure
包中官方写好的配置类。 - 3、
@EnableAutoConfiguration
是 SpringBoot 开启自动配置的核心。- 1、是由
@Import(AutoConfigurationImportSelector.class)
提供核心功能,批量往容器中导入组件。 - 2、SpringBoot 启动时会默认加载 142 个配置类,这 142 个配置类是由
spring-boot-autoconfigure
包下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件指定。 - 3、项目启动的时候利用
@Import
批量导入组件的机制,将spring-boot-autoconfigure
包下的 142 个xxxxAutoConfiguration
类导入进来(自动配置类)。 - 4、虽然导入了 142 个自动配置类,但并不是这 142 个自动配置类都能生效;每一个自动配置类,都有条件注解
@ConditionalOnxxx
,只有条件成立才能生效。
- 1、是由
- 1、
xxxxAutoConfiguration
自动配置类- 1、往容器中使用
@Bean
注册一堆组件 - 2、每个自动配置类都可能有这个注解
@EnableConfigurationProperties(ServerProperties.class)
,用来把配置文件中配的指定前缀的属性值封装到xxxProperties
属性类中。 - 3、以 Tomcat 为例,把服务器的所有配置都是以
server
开头的配置都封装到了属性类中。 - 4、往容器中放的所有组件的一些核心参数,都来自于
xxxProperties
属性类,它都是和配置文件绑定的。 - 5、只需要更改配置文件的值,核心组件的底层参数就能修改。
- 6、将精力都用于写业务,全程无需关心各种框架的整合(底层这些整合都写好了,而且也按需生效了)
- 1、往容器中使用
自动配置流程的总结
- 1、导入
spring-boot-starter-xxxx
,会导入spring-boot-starter
,也就会导入spring-boot-autoconfigure
包。 - 2、
spring-boot-autoconfigure
包里面有一个文件META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
,里面指定了应用启动时所有要加载的自动配置类。 - 3、
@EnableAutoConfiguration
会自动地将上面文件里写的所有自动配置类都导入进来,同时xxxAutoConfiguration
是有声明条件注解的,目的是按需加载。 - 4、
xxxAutoConfiguration
往容器中导入一堆组件,这些组件都是从xxxProperties
中获取属性值。 - 5、
xxxProperties
又是和配置文件进行了绑定。
最终效果:导入
starter
,修改配置文件,就能修改框架的底层行为。
深入理解
@SpringBootApplication
注解包含了三个核心注解,分别是 @ComponentScan
、@EnableAutoConfiguration
、@SpringBootConfiguration
,它们的作用如下:
@ComponentScan
- 组件扫描,排除一些组件(哪些不需要的)
- 排除前面已经扫描进来的配置类和自动配置类
@EnableAutoConfiguration
:开启自动配置功能@AutoConfigurationPackage
:扫描主程序所在的包以及其子包,加载自己的组件- 利用
@Import(AutoConfigurationPackages.Registrar.class)
,将主程序所在的包以及其子包的所有组件导入进来
- 利用
@Import(AutoConfigurationImportSelector.class)
加载所有自动配置类,会加载starter
导入的组件- 扫描 SPI 文件
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
中的所有自动配置类
- 扫描 SPI 文件
@SpringBootConfiguration
:本质就是@Configuration
注解,标注在组件类、配置类上面,Spring IOC 容器启动时就会创建加载这些类对象
SpringBoot 完整的生命周期启动加载流程如下:
SPI 机制介绍
- Java 中的 SPI(Service Provider Interface)是一种软件设计模式,用于在应用程序中动态地发现和加载组件。SPI 的思想是,定义一个接口或抽象类,然后通过在
classpath
中定义实现该接口的类来实现对组件的动态发现和加载。 - SPI 的主要目的是解决在应用程序中使用可插拔组件的问题。例如,一个应用程序可能需要使用不同的日志框架或数据库连接池,但是这些组件的选择可能取决于运行时的条件。通过使用 SPI,应用程序可以在运行时发现并加载适当的组件,而无需在代码中硬编码这些组件的实现类。
- 在 Java 中,SPI 的实现方式是通过在
META-INF/services
目录下创建一个以服务接口全限定名为名字的文件,文件中包含实现该服务接口的类的全限定名。当应用程序启动时,Java 的 SPI 机制会自动扫描classpath
中的这些文件,并根据文件中指定的类名来加载实现类。 - 通过使用 SPI,应用程序可以实现更灵活、可扩展的架构,同时也可以避免硬编码依赖关系和增加代码的可维护性。
SpringBoot 的自动配置使用了 SPI 机制,但实现方式不是在
META-INF/services
目录下创建一个以服务接口全限定名为名字的文件,而是使用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件进行替代。
功能开关介绍
自动配置
:自动批量导入,全部都配置好,什么都不用管- 项目一启动,SPI 文件中指定的所有组件都会被加载
@EnableXxxx
:手动导入,手动控制哪些功能的开启- 开启
xxx
功能 - 都是利用
@Import
注解把此功能要用的组件导入进去
- 开启
嵌入式容器
Servlet 容器指的是管理、运行 Servlet 组件(Servlet
、Filter
、Listener
)的环境,一般指 Web 服务器。
自动配置原理
- SpringBoot 默认使用嵌入的 Tomcat 作为 Servlet 容器
- 嵌入式容器的自动配置类是
ServletWebServerFactoryAutoConfiguration
,EmbeddedWebServerFactoryCustomizerAutoConfiguration
ServletWebServerFactoryAutoConfiguration
自动配置了嵌入式容器场景- 绑定了
ServerProperties
配置类,所有和服务器相关的配置都使用server
作为开始前缀 ServletWebServerFactoryAutoConfiguration
默认导入了嵌入式的三大服务器,包括Tomcat
、Jetty
、Undertow
- 导入
Tomcat
、Jetty
、Undertow
时都有条件注解,系统中有对应的类才会生效(也就是导了包) - 在默认情况下,Tomcat 的配置会生效,SpringBoot 往容器中放了
TomcatServletWebServerFactory
组件 - 往容器中放一个 Web 服务器工厂
ServletWebServerFactory
后,可以创建 Web 服务器 - Web 服务器工厂都有一个功能,可以调用
getWebServer()
获取 Web 服务器 TomcatServletWebServerFactory
创建了 Tomcat Web 服务器
- 导入
ServletWebServerApplicationContext
IOC 容器在启动的时候,会调用onRefresh()
刷新子容器- 在调用了
onRefresh()
之后,会调用ServletWebServerFactory
创建 Web 服务器
1 |
|
1 |
|
总结
- Web 场景的 Spring 容器启动,在调用
onRefresh()
的时候,会调用创建 Web 服务器的方法。 - Web 服务器的创建是通过
WebServerFactory
实现的,容器中又会根据条件注解,启动相关的服务器配置,默认EmbeddedTomcat
会往容器中放一个TomcatServletWebServerFactory
组件,导致项目启动后,自动创建出 Tomcat 服务器。
内容协商
底层原理分析
1、
@ResponseBody
的底层由HttpMessageConverter
处理数据,即标注了@ResponseBody
的返回值,将会由支持它的HttpMessageConverter
将数据返回给浏览器。
如果
Controller
方法的返回值标注了@ResponseBody
注解- 请求进来先来到
DispatcherServlet
的doDispatch()
进行处理 - 找到一个
HandlerAdapter
适配器,利用适配器执行目标方法 RequestMappingHandlerAdapter
会执行,调用invokeHandlerMethod()
来执行目标方法- 在目标方法执行之前,需要准备好两样东西
HandlerMethodArgumentResolver
:参数解析器,确定目标方法的每个参数值HandlerMethodReturnValueHandler
:返回值处理器,确定目标方法的返回值该怎么处理
RequestMappingHandlerAdapter
里面的invokeAndHandle()
真正执行目标方法- 目标方法执行完成,会返回返回值的对象
- 去找一个合适的返回值处理器
HandlerMethodReturnValueHandler
- 最终找到
RequestResponseBodyMethodProcessor
,它能处理标注了@ResponseBody
注解的方法 RequestResponseBodyMethodProcessor
调用writeWithMessageConverters()
,利用MessageConverter
把返回值输出给浏览器
- 请求进来先来到
HttpMessageConverter
会先进行内容协商- 遍历所有的
MessageConverter
,看哪个支持这种内容类型的数据 - 默认的
MessageConverter
有这些 - 最终因为需要返回 JSON 数据,所以通过
MappingJackson2HttpMessageConverter
输出 JSON 数据 - Jackson 利用
ObjectMapper
把返回值对象写出去
- 遍历所有的
2、
WebMvcAutoConfiguration
提供了 6 种 默认的HttpMessageConverters
EnableWebMvcConfiguration
通过addDefaultHttpMessageConverters
添加了默认的MessageConverter
,如下:ByteArrayHttpMessageConverter
:支持字节数据读写StringHttpMessageConverter
:支持字符串读写ResourceHttpMessageConverter
:支持资源读写ResourceRegionHttpMessageConverter
:支持分区资源写出AllEncompassingFormHttpMessageConverter
:支持表单 XML/JSON 读写MappingJackson2HttpMessageConverter
:支持请求响应体 JSON 读写
提示
SpringBoot 提供默认的 MessageConverter
功能有限,仅用于 JSON 或者普通的返回数据。如果需要增加新的内容协商功能,必须添加新的 HttpMessageConverter
。
事件和监听器
生命周期监听
自定义监听器
SpringApplicationRunListener
负责监听应用的生命周期。
- 自定义
SpringApplicationRunListener
来监听应用生命周期的步骤- 1、编写
SpringApplicationRunListener
接口的实现类 - 2、在项目的
/META-INF/spring.factories
中配置org.springframework.boot.SpringApplicationRunListener=自定义的Listener
,还可以指定一个有参构造方法,接受两个参数(SpringApplication application
,String[] args
) - 3、值得一提的是,SpringBoot 在
spring-boot.jar
中配置了默认的 Listener,即org.springframework.boot.SpringApplicationRunListener=org.springframework.boot.context.event.EventPublishingRunListener
- 1、编写
生命周期流程
- SpringBoot 应用的生命周期流程
- 1、首先从
/META-INF/spring.factories
读取到Listener
- 2、引导:利用
BootstrapContext
引导整个项目启动starting
:应用开始,SpringApplication 的run()
方法一调用,只要有了BootstrapContext
就执行environmentPrepared
:环境准备好(把启动参数等绑定到环境变量中),但是 IOC 容器还没有创建,此步骤只会执行一次
- 3、启动
contextPrepared
:IOC 容器创建并准备好,但是 Sources(主配置类)还没加载,并关闭引导上下文,组件都没创建,此步骤只会执行一次contextLoaded
:IOC 容器加载。主配置类加载进去了,但是 IOC 容器还没刷新(所有 Bean 还没创建),此步骤只会执行一次started
:IOC 容器刷新了(所有 Bean 创建好了),但是runner
没调用ready
:IOC 容器刷新了(所有 Bean 创建好了),所有runner
调用完了
- 4、运行
- 以上步骤都正确执行,代表容器成功运行
- 1、首先从
提示
关于 SpringBoot 应用的生命周期流程,更详细的说明请阅读 SpringApplication
和 SpringApplicationRunListener
的底层源码。
事件触发时机
各种事件监听器
BootstrapRegistryInitializer
:感知特定阶段(感知引导初始化)META-INF/spring.factories
- 创建引导上下文
BootstrapContext
的时候触发 application.addBootstrapRegistryInitializer()
- 使用场景:进行密钥校对授权
ApplicationContextInitializer
:感知特定阶段(感知 IOC 容器初始化)META-INF/spring.factories
application.addInitializers()
ApplicationListener
:感知全阶段,基于事件机制感知事件,一旦到了哪个阶段可以做别的事@Bean
或@EventListener
:事件驱动SpringApplication.addListeners(…)
或SpringApplicationBuilder.listeners(…)
META-INF/spring.factories
SpringApplicationRunListener
:感知全阶段生命周期,各种阶段都能自定义操作,功能更完善META-INF/spring.factories
ApplicationRunner
: 感知特定阶段(感知应用就绪 - Ready)。如果应用卡死,就不会就绪@Bean
CommandLineRunner
:感知特定阶段(感知应用就绪 - Ready)。如果应用卡死,就不会就绪@Bean
最佳实战
- 如果想要在项目启动前做事:
BootstrapRegistryInitializer
和ApplicationContextInitializer
- 如果想要在项目启动完成后做事:
ApplicationRunner
和CommandLineRunner
- 如果要干涉整个生命周期做事:
SpringApplicationRunListener
- 如果想要利用事件机制做事:
ApplicationListener
完整事件触发流程
- SpringBoot 9 大事件触发顺序与触发时机
ApplicationStartingEvent
:应用启动但未做任何事情,除了注册listeners
与initializers
ApplicationEnvironmentPreparedEvent
: Environment 准备好了,但ApplicationContext
未创建ApplicationContextInitializedEvent
:ApplicationContext
准备好了,ApplicationContextInitializers
被调用,但是任何 Bean 未加载ApplicationPreparedEvent
:容器刷新之前,Bean 定义信息加载ApplicationStartedEvent
:容器刷新完成,runner
未被调用AvailabilityChangeEvent
:LivenessState.CORRECT
应用存活探针ApplicationReadyEvent
: 任何runner
被调用AvailabilityChangeEvent
:ReadinessState.ACCEPTING_TRAFFIC
应用就绪探针,可以接收请求了ApplicationFailedEvent
:应用启动出错
探针使用说明
感知应用是否存活了
:应用可能处于植物状态,虽然活着,但是不能处理请求。应用是否就绪了
:应用可以处理外部请求,说明确实活的比较好。探针的使用场景
:若应用将来部署到 Kubernetes 等平台,则会大量被使用到。
更详细的 SpringBoot 事件触发顺序,请看这里的图解。
SpringBoot 事件驱动开发
SpringBoot 事件驱动开发,依赖应用启动过程生命周期事件感知(9 大事件)、应用运行中事件感知(无数种)。
- 事件发布:实现
ApplicationEventPublisherAware
接口,或者注入ApplicationEventMulticaster
- 事件监听:实现
ApplicationListener
接口,或者使用@EventListener
注解
SpringBoot 事件驱动开发的使用案例可以阅读 这里 的教程。
Redis 整合
自动配置原理
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
中导入了RedisAutoConfiguration
、RedisReactiveAutoConfiguration
和RedisRepositoriesAutoConfiguration
,所有属性都绑定在RedisProperties
中RedisReactiveAutoConfiguration
适用于响应式编程,RedisRepositoriesAutoConfiguration
适用于 JPA 操作RedisAutoConfiguration
配置了以下组件LettuceConnectionConfiguration
: 往容器中注入了连接工厂LettuceConnectionFactory
和操作 Redis 的客户端DefaultClientResources
RedisTemplate<Object, Object>
: 可以往 Redis 存储任意对象,默认会使用 JDK 的序列化机制StringRedisTemplate
: 可以往 Redis 存储字符串,如果需要存储对象,需要开发人员自己执行序列化操作,Key-Value 都是以字符串的形式进行操作
MyBatis 整合
自动配置原理
JDBC 场景的自动配置
- JDBC 场景的自动配置
mybatis-spring-boot-starter
导入了spring-boot-starter-jdbc
,JDBC 用于操作数据库的场景- JDBC 场景的几个自动配置
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- 支持数据源的自动配置
- 所有和数据源相关的配置都绑定在
DataSourceProperties
配置类 - 默认使用
HikariDataSource
数据源
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration
- 往容器注册
JdbcTemplate
,操作数据库
- 往容器注册
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration
- 基于 XA 二阶提交协议的分布式事务数据源
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
- 支持事务的自动配置
- 拥有的底层能力:数据源、JdbcTemplate、事务处理
MyBatisAutoConfiguration 分析
MyBatisAutoConfiguration
配置了 MyBatis 的整合流程mybatis-spring-boot-starter
导入mybatis-spring-boot-autoconfigure
(MyBatis 的自动配置包)- 默认加载两个自动配置类
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
- 必须在数据源配置好之后才配置
- 往容器注册
SqlSessionFactory
组件,用于创建访问数据库的一次会话 - 往容器注册
SqlSessionTemplate
组件,用于操作数据库
- MyBatis 的所有配置绑定在
MybatisProperties
- 每个
Mapper
接口的代理对象是怎么创建并放到容器中,详见@MapperScan
的底层源码- 利用
@Import(MapperScannerRegistrar.class)
批量往容器中注册组件。解析指定的包路径下的每一个类,为每一个Mapper
接口类创建代理对象,并注册到容器中
- 利用
如何分析哪个场景导入以后,开启了哪些自动配置类?
- 在
spring.boot.autoconfigure
包里面找classpath:/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中配置的所有值,就是要开启的自动配置类;但是每个类可能有条件注解,基于条件注解判断哪个自动配置类会生效。 - 快速定位生效的自动配置,方法如下:
1 | # 是否开启调试模式,可以详细打印开启了哪些自动配置,Positive(生效的自动配置),Negative(不生效的自动配置) |