尚硅谷SpringBoot3零基础教程
本笔记来源于:尚硅谷SpringBoot3零基础教程,springboot入门到实战
b站视频文章来自:
https://gitee.com/satan1996/springboot3-learning-notes
https://www.yuque.com/leifengyang/springboot3
一、Springboot3-快速入门
1、Springboot简介
官网:
https://spring.io/projects/spring-boot
1.Springboot是什么
Spring Boot是一个基于Spring框架的快速应用开发框架,它简化了Spring应用程序的初始搭建和开发过程。Spring Boot通过提供默认配置、自动配置和启动器等机制,使得开发者可以更加高效地构建Spring应用程序。
与传统的Spring框架不同,Spring Boot可以在不需要大量XML配置文件的情况下轻松创建应用程序,而且可以通过内嵌的Web容器(如Tomcat、Jetty或Undertow)快速部署和运行应用程序。此外,Spring Boot还支持许多其他常用框架和库,例如Spring Data JPA、Spring Security、Thymeleaf和MyBatis等。
2.Springboot的特点
- 自动配置:Spring Boot会根据classpath中的jar包、类名和注解等信息自动配置应用程序。
- 独立运行:Spring Boot应用程序可以打包成一个可执行的jar包,并直接使用java -jar命令运行。
- 内嵌服务器:Spring Boot应用程序可以使用嵌入式的Tomcat、Jetty或Undertow等内置Web服务器。
- 无需XML配置:Spring Boot大量使用Java配置和注解,避免了繁琐的XML配置。
- 微服务支持:Spring Boot支持构建微服务应用程序,并提供了一些有用的功能,例如服务发现、断路器和负载均衡等。
特性:
快速创建独立 Spring 应用
- SSM:导包、写配置、启动运行
直接嵌入Tomcat、Jetty or Undertow(无需部署 war 包)【Servlet容器】
- linux java tomcat mysql: war 放到 tomcat 的 webapps下
- jar: java环境; java -jar
重点:提供可选的starter,简化应用整合
- 场景启动器(starter):web、json、邮件、oss(对象存储)、异步、定时任务、缓存…
- 导包一堆,控制好版本。
- 为每一种场景准备了一个依赖; web-starter。mybatis-starter
重点:按需自动配置 Spring 以及 第三方库
- 如果这些场景我要使用(生效)。这个场景的所有配置都会自动配置好。
- 约定大于配置:每个场景都有很多默认配置。
- 自定义:配置文件中修改几项就可以
提供生产级特性:如 监控指标、健康检查、外部化配置等
- 监控指标、健康检查(k8s)、外部化配置
无代码生成、无xml
总结:简化开发,简化配置,简化整合,简化部署,简化监控,简化运维。
2、快速体验
场景:浏览器发送**/hello**请求,返回”Hello,Spring Boot 3!“
1. 开发流程
1. 创建项目
maven 项目
1 |
|
2. 导入场景
场景启动器
1 |
|
3. 主程序
1 |
|
4. 业务
1 |
|
5. 测试
默认启动访问: localhost:8080
6. 打包
1 |
|
mvn clean package
把项目打成可执行的jar包java -jar demo.jar
启动项目
2. 特性小结
1. 简化整合
导入相关的场景,拥有相关的功能。场景启动器
- 官方提供的场景:命名为:
spring-boot-starter-*
- 第三方提供场景:命名为:
*-spring-boot-starter
场景一导入,万物皆就绪
2. 简化开发
无需编写任何配置,直接开发业务
3. 简化配置
application.properties
:
- 集中式管理配置。只需要修改这个文件就行 。
- 配置基本都有默认值
- 能写的所有配置都在: https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties
4. 简化部署
打包为可执行的jar包。
linux服务器上有java环境。
5. 简化运维
修改配置(外部放一个application.properties文件)、监控、健康检查。
…..
3. Spring Initializr 创建向导
一键创建好整个项目结构
3、应用分析
1. 依赖管理机制
思考:
1、为什么导入starter-web
所有相关依赖都导入进来?
- 开发什么场景,导入什么场景启动器。
- maven依赖传递原则。A-B-C: A就拥有B和C
- 导入 场景启动器。 场景启动器 自动把这个场景的所有核心依赖全部导入进来
2、为什么版本号都不用写?
- 每个boot项目都有一个父项目
spring-boot-starter-parent
- parent的父项目是
spring-boot-dependencies
- 父项目 版本仲裁中心,把所有常见的jar的依赖版本都声明好了。
- 比如:
mysql-connector-j
3、自定义版本号
- 利用maven的就近原则
- 直接在当前项目
properties
标签中声明父项目用的版本属性的key - 直接在导入依赖的时候声明版本
- 直接在当前项目
4、第三方的jar包
- boot父项目没有管理的需要自行声明好
1 |
|
2. 自动配置机制
1. 初步理解
- 自动配置的 Tomcat、SpringMVC 等
- 导入场景,容器中就会自动配置好这个场景的核心组件。
- 以前:DispatcherServlet、ViewResolver、CharacterEncodingFilter….
- 现在:自动配置好的这些组件
- 验证:容器中有了什么组件,就具有什么功能
1 |
|
默认的包扫描规则
@SpringBootApplication
标注的类就是主程序类- SpringBoot只会扫描主程序所在的包及其下面的子包,自动的component-scan功能
- 自定义扫描路径
- @SpringBootApplication(scanBasePackages = “com.atguigu”)
@ComponentScan("com.atguigu")
直接指定扫描的路径
配置默认值
- 配置文件的所有配置项是和某个类的对象值进行一一绑定的。
- 绑定了配置文件中每一项值的类: 属性类。
- 比如:
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
这个包的自动配置生效、容器中就会具有相关场景的功能
2. 完整流程
思考:
1、SpringBoot怎么实现导一个starter
、写一些简单配置,应用就能跑起来,我们无需关心整合
2、为什么Tomcat的端口号可以配置在application.properties
中,并且Tomcat
能启动成功?
3、导入场景后哪些自动配置能生效?
自动配置流程细节梳理:
1、导入starter-web
:导入了web开发场景
- 1、场景启动器导入了相关场景的所有依赖:
starter-json
、starter-tomcat
、springmvc
- 2、每个场景启动器都引入了一个
spring-boot-starter
,核心场景启动器。 - 3、核心场景启动器引入了
spring-boot-autoconfigure
包。 - 4、
spring-boot-autoconfigure
里面囊括了所有场景的所有配置。 - 5、只要这个包下的所有类都能生效,那么相当于SpringBoot官方写好的整合功能就生效了。
- 6、SpringBoot默认却扫描不到
spring-boot-autoconfigure
下写好的所有配置类。(这些配置类给我们做了整合操作),默认只扫描主程序所在的包。
2、主程序:@SpringBootApplication
-1、@SpringBootApplication
由三个注解组成@SpringBootConfiguration
、@EnableAutoConfiguratio
、@ComponentScan
2、SpringBoot默认只能扫描自己主程序所在的包及其下面的子包,扫描不到
spring-boot-autoconfigure
包中官方写好的配置类3、
@EnableAutoConfiguration
:SpringBoot 开启自动配置的核心。- 是由
@Import(AutoConfigurationImportSelector.class)
提供功能:批量给容器中导入组件。
- 是由
- SpringBoot启动会默认加载 142个配置类。
- 这142个配置类来自于
spring-boot-autoconfigure
下META-INF/spring/**org.springframework.boot.autoconfigure.AutoConfiguration**.imports
文件指定的
- 这142个配置类来自于
- 项目启动的时候利用 @Import 批量导入组件机制把
autoconfigure
包下的142xxxxAutoConfiguration
类导入进来(自动配置类) - 虽然导入了
142
个自动配置类,但是不是全部生效,见下行4中介绍。
4、按需生效:
- 并不是这
142
个自动配置类都能生效 - 每一个自动配置类,都有条件注解
@ConditionalOnxxx
,只有条件成立,才能生效
- 并不是这
3、xxxxAutoConfiguration
自动配置类
- 1、给容器中使用@Bean 放一堆组件。
- 2、每个自动配置类都可能有这个注解
@EnableConfigurationProperties(ServerProperties.class)
,用来把配置文件中配的指定前缀的属性值封装到xxxProperties
属性类中 - 3、以Tomcat为例:把服务器的所有配置都是以
server
开头的。配置都封装到了属性类中。 - 4、给容器中放的所有组件的一些核心参数,都来自于
xxxProperties
。xxxProperties
都是和配置文件绑定。 - 只需要改配置文件的值,核心组件的底层参数都能修改
4、写业务,全程无需关心各种整合(底层这些整合写好了,而且也生效了)
核心流程总结:
1、导入starter
,就会导入autoconfigure
包。
2、autoconfigure
包里面 有一个文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
,里面指定的所有启动要加载的自动配置类
3、@EnableAutoConfiguration 会自动的把上面文件里面写的所有自动配置类都导入进来。xxxAutoConfiguration 是有条件注解进行按需加载
4、xxxAutoConfiguration
给容器中导入一堆组件,组件都是从 xxxProperties
中提取属性值
5、xxxProperties
又是和配置文件进行了绑定
效果:导入starter
、修改配置文件,就能修改底层行为。
3、自动配置原理
1.@SpringBootApplication
一切的来自起源SpringBoot(3.1.0)的启动类,我们发现main方法上面有个注解:@SpringBootApplication
1 |
|
@SpringBootApplication
标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的main方法来启动 SpringBoot 应用;它的本质是一个组合注解,我们点进去查看该类的元信息主要包含3个注解:
1 |
|
@SpringBootConfiguration
(里面就是@Configuration,标注当前类为配置类,其实只是做了一层封装改了个名字而已)@EnableAutoConfiguration
(开启自动配置)@ComponentScan
(包扫描)
注:**@Inherited是一个标识,用来修饰注解,如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解**
我们下面逐一分析这3个注解作用
1.1.@SpringBootConfiguration
- 我们继续点
@SpringBootConfiguration
进去查看源码如下:
1 |
|
@Configuration
标注在某个类上,表示这是一个 springboot的配置类
。可以向容器中注入组件。
1.2 @ComponentScan
@ComponentScan
:配置用于 Configuration 类的组件扫描指令。- 提供与
Spring XML
的<context:component-scan>
元素并行的支持。 - 可以
basePackageClasses
或basePackages
来定义要扫描的特定包。 如果没有定义特定的包,将从声明该注解的类的包开始扫描
。
1.3 @EnableAutoConfiguration
- @EnableAutoConfiguration顾名思义就是:
开启自动导入配置
- 这个注解是SpringBoot的重点,我们下面详细讲解
2.@EnableAutoConfiguration
我们点进去看看该注解有什么内容
1 |
|
2.1.@AutoConfigurationPackage
- 自动导入配置包
- 点进去查看代码:
1 |
|
它的作用是让 Spring Boot 自动扫描指定的包或类所在的包,以便自动配置相关的 Bean 或组件。
Import
为spring的注解,导入一个配置文件,在springboot中为给容器导入一个组件,而导入的组件由 AutoConfigurationPackages.class的内部类Registrar.class
执行逻辑来决定是如何导入的。
2.1.1 @Import({Registrar.class})
点Registrar.class进去查看源码如下:
1 |
|
注:Registrar实现了ImportBeanDefinitionRegistrar
类,就可以被注解@Import导入到spring容器里。
这个地方打断点
运行可以查看到AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));的值为当前启动类所在的包名
结论:**@AutoConfigurationPackage 就是将主配置类(@SpringBootApplication 标注的类)所在的包下面所有的组件都扫描注冊到 spring 容器中。**
2.2.@Import({AutoConfigurationImportSelector.class})
作用:AutoConfigurationImportSelector开启自动配置类的导包的选择器
,即是带入哪些类,有选择性的导入。
点AutoConfigurationImportSelector.class进入查看源码,这个类中有两个方法见名知意:
selectImports:用于根据注解元数据获取自动配置类并返回其全限定名数组。
具体来说,
selectImports()
方法会先检查当前环境中是否启用了自动配置,如果没有启用,则会直接返回空数组。否则,它会调用getAutoConfigurationEntry()
方法获取自动配置条目,并通过getConfigurations()
方法获取候选的自动配置类。最后,通过StringUtils.toStringArray()
方法将自动配置类列表转换为字符串数组并返回。总之,该方法主要用于在应用程序启动时加载自动配置类,进而实现对各种功能的统一、标准化和协作式管理。
1
2
3
4
5
6
7
8public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}getAutoConfigurationEntry:具体来说,
getAutoConfigurationEntry()
方法根据当前环境中是否启用了自动配置来决定是否返回空对象。如果启用了自动配置,则从注解元数据annotationMetadata
和注解属性attributes
中获取候选的自动配置类。然后,通过removeDuplicates()
方法删除重复项,再通过getExclusions()
方法获取需要排除的依赖,并将其从候选的自动配置类中移除。接着,通过filter()
方法筛选出需要保留的自动配置类。最后,调用fireAutoConfigurationImportEvents()
方法触发自动配置导入事件,以便其他组件也能对自动配置进行监听和处理。总之,该方法是 Spring Boot 自动配置机制的核心,通过自动扫描、检测和筛选等步骤,实现了自动配置的灵活、高效和可靠。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 这打个断点,看看 返回的数据
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//删除重复项
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
//检查
this.checkExcludedClasses(configurations, exclusions);
//删除需要排除的依赖
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
configurations数组长度为150,并且文件后缀名都为 **AutoConfiguration
结论: 这些都是候选的配置类,经过去重,去除需要的排除的依赖,最终的组件才是这个环境需要的所有组件。有了自动配置,就不需要我们自己手写配置的值了,配置类有默认值的。
我们继续往下看看是如何返回需要配置的组件的
2.2.1.getCandidateConfigurations(annotationMetadata, attributes)
方法如下:
1 |
|
这是 Spring Boot 自动配置的核心代码之一,用于获取自动配置类的候选列表。
具体来说,getCandidateConfigurations()
方法会通过 ImportCandidates.load()
方法,从指定路径 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
中加载所有 AutoConfiguration
类型的候选配置。然后,它会检查是否加载到了自动配置类,并在没有找到时抛出异常。最后,将所有候选配置(即自动配置类)返回。
总之,该方法是 Spring Boot 自动配置机制的重要部分,通过自动扫描和加载,实现了对各种功能的快速、便捷和精准的自动配置。
org.springframework.boot.autoconfigure.AutoConfiguration.imports文件位置如下:
- 由图可知文件内包含146个自动配置类的全类名
- 并不是这146个自动配置类都能生效
- 每一个自动配置类,都有条件注解
@ConditionalOnxxx
,只有条件成立,才能生效
3.流程总结图
4.常用的Conditional注解
- 在加载自动配置类的时候,并不是将spring.factories的配置全部加载进来,而是通过@Conditional等注解的判断进行动态加载
- @Conditional其实是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。
- 常用的Conditional注解:
- @ConditionalOnClass : classpath中存在该类时起效
- @ConditionalOnMissingClass : classpath中不存在该类时起效
- @ConditionalOnBean : DI容器中存在该类型Bean时起效
- @ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
- @ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
- @ConditionalOnExpression : SpEL表达式结果为true时
- @ConditionalOnProperty : 参数设置或者值一致时起效
- @ConditionalOnResource : 指定的文件存在时起效
- @ConditionalOnJndi : 指定的JNDI存在时起效
- @ConditionalOnJava : 指定的Java版本存在时起效
- @ConditionalOnWebApplication : Web应用环境下起效
- @ConditionalOnNotWebApplication : 非Web应用环境下起效
5.@Import支持导入的三种方式
- 带有@Configuration的配置类
- ImportSelector 的实现
- ImportBeanDefinitionRegistrar 的实现
4. 如何学好SpringBoot
框架的框架、底层基于Spring。能调整每一个场景的底层行为。100%项目一定会用到底层自定义
摄影:
- 傻瓜:自动配置好。
- 单反:焦距、光圈、快门、感光度….
- 傻瓜+单反:
- 理解自动配置原理
- 导入starter –> 生效xxxxAutoConfiguration –> 组件 –> xxxProperties –> 配置文件
- 理解其他框架底层
- 拦截器
- 可以随时定制化任何组件
- 配置文件
- 自定义组件
普通开发:导入starter
,Controller、Service、Mapper、偶尔修改配置文件
高级开发:自定义组件、自定义配置、自定义starter
核心:
- 这个场景自动配置导入了哪些组件,我们能不能Autowired进来使用
- 能不能通过修改配置改变组件的一些默认参数
- 需不需要自己完全定义这个组件
- 场景定制化
最佳实战:
选场景,导入到项目
- 官方:starter
- 第三方:去仓库搜
写配置,改配置文件关键项
- 数据库参数(连接地址、账号密码…)
分析这个场景给我们导入了哪些能用的组件
- 自动装配这些组件进行后续使用
- 不满意boot提供的自动配好的默认组件
- 定制化
- 改配置
- 自定义组件
- 定制化
整合redis:
选场景:
spring-boot-starter-data-redis
- 场景AutoConfiguration 就是这个场景的自动配置类
写配置:
- 分析到这个场景的自动配置类开启了哪些属性绑定关系
@EnableConfigurationProperties(RedisProperties.class)
- 修改redis相关的配置
分析组件:
- 分析到
RedisAutoConfiguration
给容器中放了StringRedisTemplate
- 给业务代码中自动装配
StringRedisTemplate
- 分析到
定制化
- 修改配置文件
- 自定义组件,自己给容器中放一个
StringRedisTemplate
4、常用注解
SpringBoot摒弃XML配置方式,改为全注解驱动
1.组件注册
@Configuration、**@SpringBootConfiguration**
@Bean、**@Scope**
@Controller、 @Service、@Repository、@Component
@Import
@ComponentScan
步骤:
1、@Configuration 编写一个配置类
2、在配置类中,自定义方法给容器中注册组件。配合@Bean
3、或使用@Import 导入第三方的组件
1.1.@Configuration、
@SpringBootConfiguration
1 |
|
在上面的示例中,我们使用 @Configuration
注解来声明一个 Spring 配置类,并在其中定义了一个名为 myService()
的 Bean。这个 Bean 交由 Spring 容器进行托管管理,可以通过容器获取它并调用它的方法。
1.2.@Bean、
@Scope
1 |
|
在上面的示例中,我们使用 @Bean
注解来创建名为 myService
的 Bean 对象,并指定它的作用域为 prototype
,表示每次获取该 Bean 时都会创建一个新的实例对象。这个 Bean 会被 Spring 容器托管管理,可以在其它组件中使用。
1.3.@Controller、
@Service、
@Repository、
@Component
1 |
|
在上面的示例中,我们分别使用 @Service
和 @Repository
注解来声明服务和数据访问组件,这些组件会被 Spring 容器进行托管管理,并可以在其它组件中自动注入。另外,@Controller
注解也可以用于声明控制器组件,@Component
则是一个通用的组件注解。
1.4.@Import
1 |
|
在上面的示例中,我们使用 @Import
注解来导入另外两个配置类 DaoConfig
和 ServiceConfig
,以便在当前应用程序上下文中注册相关的 Bean 或配置。
1.5.@ComponentScan
1 |
|
在上面的示例中,我们使用 @ComponentScan
注解配置 Spring 组件的扫描范围,指定了 com.example.project.service
和 com.example.project.dao
两个包及其子包中的组件都会被扫描到并注册为 Bean。
1.6.@component
@Component
是 Spring 框架中的一个注解,它表示当前标注的类是一个组件类,需要被 Spring 管理。通常情况下,我们使用 @Component
注解来声明一个普通的 bean,这个 bean 可以在整个应用程序中被其他组件引用和使用。
具体来说,使用 @Component
注解需要按照以下步骤进行:
- 在需要被 Spring 管理的 Java 类上添加
@Component
注解。 - 在需要使用这个 bean 的地方使用
@Autowired
注解进行自动注入。
例如,下面是一个简单的例子:
1 |
|
在上述代码中,我们定义了一个名为 MyService
的 bean,它使用了 @Component
注解,表示这是一个 Spring 管理的组件,并且可以被其他组件引用和使用。该 bean 中有一个名为 sayHello()
的方法,用于输出一段简单的问候语。
接着,我们可以在需要使用 MyService
的组件中使用 @Autowired
注解来自动注入该 bean:
1 |
|
在上述代码中,我们声明了一个名为 MyServiceImpl
的服务实现类,并使用 @Service
注解表示这是一个服务组件。在该组件中,我们使用 @Autowired
注解自动注入了 MyService
bean,并在 hello()
方法中调用了该 bean 的 sayHello()
方法。
总之,@Component
是 Spring 中最基本的注解之一,它让我们可以轻松地声明和管理 bean,并且利用 Spring 框架提供的依赖注入机制来进行组件之间的协作。
2.条件注解
如果注解指定的条件成立,则触发指定行为
@ConditionalOnXxx
@ConditionalOnClass:如果类路径中存在这个类,则触发指定行为
@ConditionalOnMissingClass:如果类路径中不存在这个类,则触发指定行为
@ConditionalOnBean:如果容器中存在这个Bean(组件),则触发指定行为
@ConditionalOnMissingBean:如果容器中不存在这个Bean(组件),则触发指定行为
场景:
- 如果存在
FastsqlException
这个类,给容器中放一个Cat
组件,名cat01,- 否则,就给容器中放一个
Dog
组件,名dog01- 如果系统中有
dog01
这个组件,就给容器中放一个 User组件,名zhangsan- 否则,就放一个User,名叫lisi
@ConditionalOnBean(value=组件类型,name=组件名字):判断容器中是否有这个类型的组件,并且名字是指定的值
@ConditionalOnRepositoryType (org.springframework.boot.autoconfigure.data)
@ConditionalOnDefaultWebSecurity (org.springframework.boot.autoconfigure.security)
@ConditionalOnSingleCandidate (org.springframework.boot.autoconfigure.condition)
@ConditionalOnWebApplication (org.springframework.boot.autoconfigure.condition)
@ConditionalOnWarDeployment (org.springframework.boot.autoconfigure.condition)
@ConditionalOnJndi (org.springframework.boot.autoconfigure.condition)
@ConditionalOnResource (org.springframework.boot.autoconfigure.condition)
@ConditionalOnExpression (org.springframework.boot.autoconfigure.condition)
@ConditionalOnClass (org.springframework.boot.autoconfigure.condition)
@ConditionalOnEnabledResourceChain (org.springframework.boot.autoconfigure.web)
@ConditionalOnMissingClass (org.springframework.boot.autoconfigure.condition)
@ConditionalOnNotWebApplication (org.springframework.boot.autoconfigure.condition)
@ConditionalOnProperty (org.springframework.boot.autoconfigure.condition)
@ConditionalOnCloudPlatform (org.springframework.boot.autoconfigure.condition)
@ConditionalOnBean (org.springframework.boot.autoconfigure.condition)
@ConditionalOnMissingBean (org.springframework.boot.autoconfigure.condition)
@ConditionalOnMissingFilterBean (org.springframework.boot.autoconfigure.web.servlet)
@Profile (org.springframework.context.annotation)
@ConditionalOnInitializedRestarter (org.springframework.boot.devtools.restart)
@ConditionalOnGraphQlSchema (org.springframework.boot.autoconfigure.graphql)
@ConditionalOnJava (org.springframework.boot.autoconfigure.condition)
Spring Boot 中的条件注解是一种特殊的注解,当满足特定条件时才会创建或注册一个 bean 或配置类。这种方式可以让我们根据应用程序当前的环境、配置、状态等情况来动态地决定 bean 和配置类是否需要被创建或注册,以及创建或注册的方式和内容。
2.1.@Conditional
在 Spring Boot 中,条件注解通常都是以 @Conditional
开头的,它们都实现了 Condition
接口。使用条件注解一般有以下几个步骤:
创建一个 Java 类,实现
Condition
接口,实现matches()
方法来判断是否满足特定条件。如下面的例子:1
2
3
4
5
6
7
8public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String os = conditionContext.getEnvironment().getProperty("os.name");
return os != null && os.toLowerCase().contains("linux");
}
}在需要进行条件化配置的 bean 或配置类上添加条件注解,配合上面实现的
Condition
类进行动态配置。如下面的例子:1
2
3
4
5
6
7
8
9@Configuration
@Conditional(LinuxCondition.class)
public class LinuxServiceConfig {
@Bean
public MyService myService() {
return new LinuxService();
}
}
在上述代码中,我们定义了一个名为 LinuxServiceConfig
的配置类,使用 @Conditional(LinuxCondition.class)
注解来根据当前系统是 Linux 系统还是其它操作系统来决定是否创建 LinuxService
bean。如果当前系统是 Linux 系统,那么就会创建 LinuxService
bean,并且将其注册为 MyService
类型的 bean。
值得注意的是,条件注解并不是只有 @Conditional
一种,Spring Boot 还提供了很多实现了 Condition
接口的注解,如 @ConditionalOnBean
、@ConditionalOnMissingBean
、@ConditionalOnClass
、@ConditionalOnWebApplication
等。
2.2.@ConditionalOnBean
@ConditionalOnBean
注解会检查指定的 Bean 是否存在于 Spring 容器中,如果存在,则自动配置当前 Bean。
例如:
1 |
|
从上面的例子中,我们可以看出当 DataSource
这个 Bean 存在时,才会进行 jdbcTemplate
的自动配置。
2.3.@ConditionalOnMissingBean
@ConditionalOnMissingBean
注解会先检查指定的名称的 Bean 在 Spring 容器中是否存在,如果不存在,则自动配置当前 Bean。
例如:
1 |
|
从上面的例子中,我们可以看出,只有当 JdbcTemplate
这个 Bean 不存在时,才会进行 jdbcTemplate
的自动配置。
2.4.@ConditionalOnClass
@ConditionalOnClass
注解会检查指定的类是否存在于项目的 ClassPath 中,如果存在,则自动配置当前 Bean。
例如:
1 |
|
从上面的例子中,我们可以看出当 com.alibaba.dubbo.config.RegistryConfig
这个类存在时,才会自动配置 MyConfig
。
2.5.@ConditionalOnWebApplication
@ConditionalOnWebApplication
注解会检查当前项目是否为 Web 应用程序,并且支持过滤 Servlet 和 Reactive 应用程序。
例如:
1 |
|
从上面的例子中,我们可以看出当当前项目为 Servlet 应用程序时,才会自动配置 MyConfig
。
综上所述,Spring Boot 中的条件注解提供了一种优雅、灵活的使用方式以进行自动化配置,减少了冗余的代码,并大大增强了系统的可扩展性。
3.属性绑定
@ConfigurationProperties: 声明组件的属性和配置文件哪些前缀开始项进行绑定
@EnableConfigurationProperties:快速注册注解:
- 场景:SpringBoot默认只扫描自己主程序所在的包。如果导入第三方包,即使组件上标注了 @Component、@ConfigurationProperties 注解,也没用。因为组件都扫描不进来,此时使用这个注解就可以快速进行属性绑定并把组件注册进容器
将容器中任意组件(Bean)的属性值和配置文件的配置项的值进行绑定
- 1、给容器中注册组件(@Component、@Bean)
- 2、使用@ConfigurationProperties 声明组件和配置文件的哪些配置项进行绑定
更多注解参照:Spring注解驱动开发【1-26集】
3.1.@Value
@Value
可以用于注入单个属性值,支持SpEL表达式,可以直接将配置文件中的值注入到Java对象中
例如:
创建实体类
1
2
3
4
5
6
7
8
9
10
11public class Dog {
@Value("${dog.name}")
private String name;
@Value("${dog.age}")
private Integer age;
public Dog() {
}
//省略toString()、Get、Set方法、构造方法
}配置文件
1 |
|
配置类
1
2
3
4
5
6
7@Configuration
public class AppConfig4 {
@Bean
public Dog dog() {
return new Dog();
}
}使用
1
2
3
4
5public static void main(String[] args) {
var applicationContext=SpringApplication.run(Boot303DemoApplication.class, args);
Dog dog=applicationContext.getBean(Dog.class);
System.out.println("dog = " + dog);
}
3.2.@ConfigurationProperties
@ConfigurationProperties
可以将一个或多个配置文件中的属性映射到Java对象中,也可以使用前缀来匹配属性。
例如:
创建实体类
1
2
3
4
5
6
7
8
9
10
11
12
13package com.satan.boot.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "user")
public class User {
private String name;
private int age;
//省略toString()、Get、Set方法、构造方法
}配置文件
1 |
|
配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29package com.satan.boot.config;
import com.satan.boot.bean.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* @ClassName AppConfig3
* @Description TODO
* @date 2023/6/3 18:29
* @Version 1.0
*/
@Configuration
public class AppConfig {
@Bean
public User user(){
return new User();
}
//或者
@Bean
@ConfigurationProperties(prefix = "user")//将此注解从实体类移动到此处
public User user(){
return new User();
}
}使用
1
2
3
4
5public static void main(String[] args) {
var applicationContext = SpringApplication.run(Boot303DemoApplication.class, args);
User user = applicationContext.getBean(User.class);
System.out.println("user = " + user);
}
3.3.@EnableConfigurationProperties
@EnableConfigurationProperties
用于开启@ConfigurationProperties
注解的自动配置功能。
例如:
创建实体类
1
2
3
4
5
6
7
8
9
10
11
12
13package com.satan.boot.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private int age;
//省略toString()、Get、Set方法、构造方法
}配置文件
1 |
|
- 配置类
1 |
|
- 使用
1 |
|
3.4.@PropertySource
@PropertySource
用于指定加载的外部属性文件,可以使用${}表达式引用外部文件中的属性值。
例如:
创建实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.satan.boot.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @ClassName Student
* @Description TODO
* @date 2023/6/3 18:13
* @Version 1.0
*/
@ConfigurationProperties(value = "student")
public class Student {
private String name;
private int age;
//省略toString()、Get、Set方法、构造方法
}配置文件
1 |
|
- 配置类
1 |
|
- 使用
1 |
|
5、YAML配置文件
1. yaml 简介
YAML 是 “YAML Ain’t a Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:”Yet Another Markup Language”(仍是一种标记语言)。
YAML 的语法和其他高级语言类似,并且可以简单表达清单、散列表,标量等数据形态。它使用空白符号缩进和大量依赖外观的特色,特别适合用来表达或编辑数据结构、各种配置文件、倾印调试内容、文件大纲(例如:许多电子邮件标题格式和 YAML 非常接近)。
YAML 的配置文件后缀为 .yml 或 .yaml 。
2.基本语法
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用
Tab
,只允许空格(IDEA 除外,因为 IDEA 会自动将 Tab 转换为空格) - 缩进的空格数不重要,只要相同层级的元素左对齐即可
#
:表示注释- 字符串无需加引号,
' '
与" "
表示的字符串内容分别被转义与不转义
3.数据类型
YAML 支持以下几种数据类型:
- 字面量:单个、不可再分的值【date、boolean、string、number、null】
- 对象:键值对的集合【map、object】
- 数组:一组按次序排列的值【array、list、set、queue】
3.1 .字面量
1 |
|
在 YAML 文件中,可以使用 |
或 >
表示多行文本,如下所示:
1 |
|
在上述示例中,message
属性的值为一个多行文本,使用 |
表示。如果要忽略换行符,可以使用 >
。
3.2.对象
1 |
|
较为复杂的对象格式,可以使用问号加一个空格代表一个复杂的 key,配合一个冒号加一个空格代表一个 value:
1 |
|
意思即对象的属性是一个数组 [complexkey1, complexkey2]
,对应的值也是一个数组 [complexvalue1, complexvalue2]
3.3.数组
以 -
开头的行表示构成一个数组:
1 |
|
YAML 支持多维数组,可以使用行内表示:
1 |
|
一个相对复杂的例子 List<Person> companies
:
1 |
|
意思是 companies 属性是一个数组,每一个数组元素又是由 id、name、price 三个属性构成。
数组也可以使用 {流式} 的方式表示:
1 |
|
3.4.引用
&
:锚点
*
:别名
<<
:合并到当前数据
1 |
|
相当于:
1 |
|
下面是另一个例子:
1 |
|
转为 JavaScript 代码如下:
1 |
|
3.5.例子
举一个比较全面的例子,弄清楚就好了。
1 |
|
对照 application.yml:
1 |
|
结果:
1 |
|
3.6.配置提示
自定义的类和配置文件绑定没有提示,所以我们需要手动设置。
pom.xml:
1 |
|
4. 细节
birthDay 推荐写为 birth-day
文本:
- 单引号不会转义【\n 则为普通字符串显示】
- 双引号会转义【\n会显示为换行符】
大文本
|
开头,大文本写在下层,保留文本格式,换行符正确显示>
开头,大文本写在下层,折叠换行符
多文档合并
- 使用
---
可以把多个yaml文档合并在一个文档中,每个文档区依然认为内容独立
- 使用
5. 小技巧:lombok
简化JavaBean 开发。自动生成构造器、getter/setter、自动生成Builder模式等
1 |
|
使用@Data
等注解
6、日志配置
1.简介
- Spring使用commons-logging作为内部日志,但底层日志实现是开放的。可对接其他日志框架。
- spring5及以后 commons-logging被spring直接自己写了。
- 支持 jul,log4j2,logback。SpringBoot 提供了默认的控制台输出配置,也可以配置输出为文件。
- logback是默认使用的。
- 虽然日志框架很多,但是我们不用担心,使用 SpringBoot 的默认配置就能工作的很好。
SpringBoot怎么把日志默认配置好的
1、每个starter
场景,都会导入一个核心场景spring-boot-starter
2、核心场景引入了日志的所用功能spring-boot-starter-logging
3、默认使用了logback + slf4j
组合作为默认底层日志
4、日志是系统一启动就要用
,xxxAutoConfiguration
是系统启动好了以后放好的组件,后来用的。
5、日志是利用监听器机制配置好的。ApplicationListener
。
6、日志所有的配置都可以通过修改配置文件实现。以logging
开始的所有配置。
2.日志格式
Springboot的默认日志输出类似于以下示例:
1 |
|
默认输出格式:
- 时间和日期:毫秒级精度
- 日志级别:ERROR, WARN, INFO, DEBUG, or TRACE.
- 进程 ID
- —: 消息分割符
- 线程名: 使用[]包含
- Logger 名: 通常是产生日志的类名
- 消息: 日志记录的内容
注意: logback 没有FATAL级别,对应的是ERROR
默认值:参照:spring-boot
包additional-spring-configuration-metadata.json
文件
默认输出格式值:%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
可修改为:
1 |
|
3.记录日志
1 |
|
或者使用Lombok的@Slf4j注解
1 |
|
4.日志级别
由低到高:
ALL,TRACE, DEBUG, INFO, WARN, ERROR,FATAL,OFF
;- 只会打印指定级别及以上级别的日志
- ALL:打印所有日志
- TRACE:追踪框架详细流程日志,一般不使用
- DEBUG:开发调试细节日志
- INFO:关键、感兴趣信息日志
- WARN:警告但不是错误的信息日志,比如:版本过时
- ERROR:业务错误日志,比如出现各种异常
- FATAL:致命错误日志,比如jvm系统崩溃
- OFF:关闭所有日志记录
不指定级别的所有类,都使用root指定的级别作为默认级别
SpringBoot日志默认级别是 INFO
在application.properties/yaml中配置logging.level.<logger-name>=<level>指定日志级别
1
2
3logging:
level:
com.satan.boot.controller: info此时控制台只能打印info级别及高于info级别以上的日志
level可取值范围:
TRACE, DEBUG, INFO, WARN, ERROR, FATAL, or OFF
,定义在LogLevel
类中root 的
logger-name
叫root
,可以配置logging.level.root=warn
,代表所有未指定日志级别都使用 root 的 warn 级别
5.日志分组
比较有用的技巧是:
将相关的logger分组在一起,统一配置。SpringBoot 也支持。比如:Tomcat 相关的日志统一设置
1 |
|
SpringBoot 预定义两个组
Name | Loggers |
---|---|
web | org.springframework.core.codec, org.springframework.http, org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans |
sql | org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener |
日志分组机制允许您对不同的模块或组件生成的日志信息进行分类,并且可以为每个分类设置不同的日志级别和输出目标。这样,您就可以更加灵活地控制和管理您的应用程序日志信息。
下面是一个使用日志分组机制的示例:
1 |
|
上面的示例定义了两个日志分组,分别是myapp和mylib。myapp分组包括了两个包:com.example.package1和com.example.package2;mylib分组包括了两个包:com.example.lib1和com.example.lib2。同时,为每个分组分别设置了不同的日志级别和输出目标。对于myapp分组,设置了DEBUG级别并将日志输出到/var/log/myapp.log文件;对于mylib分组,设置了INFO级别并将日志输出到/var/log/mylib.log文件。
6.文件输出
SpringBoot 默认只把日志写在控制台,如果想额外记录到文件,可以在application.properties
中添加logging.file.name
or logging.file.path
配置项。
logging.file.name | logging.file.path | 示例 | 效果 |
---|---|---|---|
未指定 | 未指定 | 仅控制台输出 | |
指定 | 未指定 | my.log | 写入指定文件。可以加路径 |
未指定 | 指定 | /var/log | 写入指定目录,文件名为spring.log |
指定 | 指定 | 以logging.file.name为准 |
logging.file.name
:设置日志文件的名称,该文件位于应用程序的工作目录中。
logging.file.path
:指定存储日志文件的完整路径,包括文件名。如果同时指定了 logging.file.path
和 logging.file.name
,则优先使用 logging.file.path
。
以下是使用这些配置项的示例:
1 |
|
7.文件归档与滚动切割
归档:每天的日志单独存到一个文档中。
切割:每个文件10MB,超过大小切割成另外一个文件。
- 每天的日志应该独立分割出来存档。如果使用logback(SpringBoot 默认整合),可以通过application.properties/yaml文件指定日志滚动规则。
- 如果是其他日志系统,需要自行配置(添加log4j2.xml或log4j2-spring.xml)
- 支持的滚动规则设置如下
配置项 | 描述 |
---|---|
logging.logback.rollingpolicy.file-name-pattern | 日志存档的文件名格式(默认值:${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz) |
logging.logback.rollingpolicy.clean-history-on-start | 应用启动时是否清除以前存档(默认值:false) |
logging.logback.rollingpolicy.max-file-size | 存档前,每个日志文件的最大大小(默认值:10MB) |
logging.logback.rollingpolicy.total-size-cap | 日志文件被删除之前,可以容纳的最大大小(默认值:0B)。设置1GB则磁盘存储超过 1GB 日志后就会删除旧日志文件 |
logging.logback.rollingpolicy.max-history | 日志文件保存的最大天数(默认值:7). |
8.自定义配置
通常我们配置 application.properties 就够了。当然也可以自定义。比如:
日志系统 | 自定义 |
---|---|
Logback | logback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy |
Log4j2 | log4j2-spring.xml or log4j2.xml |
JDK (Java Util Logging) | logging.properties |
如果可能,我们建议您在日志配置中使用-spring
变量(例如,logback-spring.xml
而不是 logback.xml
)。如果您使用标准配置文件,spring 无法完全控制日志初始化。
最佳实战:自己要写配置,配置文件名加上 xx-spring.xml
- 创建 Logback 配置文件
在项目的 src/main/resources 目录下创建一个 logback-spring.xml 文件(并编写自定义的 Logback 配置。例如:
1 |
|
上述配置定义了一个名为 CONSOLE 的 Appender,将日志输出到控制台。同时定义了一个名为 ROOT 的 Logger,将日志级别设为 INFO,输出到 CONSOLE Appender 中。具体的日志格式可以根据自己的需求进行调整。
- 配置 Spring Boot 应用程序
在 application.properties 或 application.yml 配置文件中指定使用自定义的 Logback 配置文件:
1 |
|
- 自定义配置常用
1 |
|
9. 切换日志组合
1 |
|
log4j2支持yaml和json格式的配置文件
格式 | 依赖 | 文件名 |
---|---|---|
YAML | com.fasterxml.jackson.core:jackson-databind + com.fasterxml.jackson.dataformat:jackson-dataformat-yaml | log4j2.yaml + log4j2.yml |
JSON | com.fasterxml.jackson.core:jackson-databind | log4j2.json + log4j2.jsn |
10. 最佳实战
- 导入任何第三方框架,先排除它的日志包,因为Boot底层控制好了日志
- 修改
application.properties
配置文件,就可以调整日志的所有行为。如果不够,可以编写日志框架自己的配置文件放在类路径下就行,比如logback-spring.xml
,log4j2-spring.xml
- 如需对接专业日志系统,也只需要把 logback 记录的日志灌倒 kafka之类的中间件,这和SpringBoot没关系,都是日志框架自己的配置,修改配置文件即可
- 业务中使用slf4j-api记录日志。不要再 sout 了
二、web开发
1.web场景
1.1.自动配置
1、整合web场景
1 |
|
2、引入了 autoconfigure
功能
1 |
|
3、@EnableAutoConfiguration
注解使用@Import(AutoConfigurationImportSelector.class)
批量导入组件
4、加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中配置的所有组件
5、所有自动配置类如下
1 |
|
6、绑定了配置文件的一堆配置项
- 1、SpringMVC的所有配置
spring.mvc
- 2、Web场景通用配置
spring.web
- 3、文件上传配置
spring.servlet.multipart
- 4、服务器的配置
server
: 比如:编码方式
1.2.默认效果
默认配置:
- 包含了 ContentNegotiatingViewResolver 和 BeanNameViewResolver 组件,方便视图解析
- 默认的静态资源处理机制: 静态资源放在 static 文件夹下即可直接访问
- 自动注册了 Converter,GenericConverter,Formatter组件,适配常见数据类型转换和格式化需求
- 支持 HttpMessageConverters,可以方便返回json等数据类型
- 注册 MessageCodesResolver,方便国际化及错误消息处理
- 支持 静态 index.html
- 自动使用ConfigurableWebBindingInitializer,实现消息处理、数据绑定、类型转化、数据校验等功能
重要:
- 如果想保持 boot mvc 的默认配置*,并且自定义更多的 mvc 配置,如:interceptors*, formatters*,* view controllers** *等。可以使用@Configuration*注解添加一个 WebMvcConfigurer 类型的配置类,并不要标注 @EnableWebMvc
- 如果想保持 boot mvc 的默认配置,但要自定义核心组件实例,比如:*RequestMappingHandlerMapping,* RequestMappingHandlerAdapter, 或ExceptionHandlerExceptionResolver,给容器中放一个 WebMvcRegistrations 组件即可
- 如果想全面接管 Spring MVC,@Configuration 标注一个配置类,并加上 @EnableWebMvc**注解,实现 WebMvcConfigurer 接口
2.WebMvcAutoConfiguration原理
2.1.生效条件
1 |
|
这段代码是 Spring Boot 中自动配置 Web MVC 的核心类
@AutoConfiguration(after={...})
:表明这个自动配置类在另外三个自动配置类之后启用。具体来说,它会在使用DispatcherServletAutoConfiguration
、TaskExecutionAutoConfiguration
和ValidationAutoConfiguration
进行自动配置之后再进行自动配置。@ConditionalOnWebApplication(type=Type.SERVLET)
:表明这个自动配置类只会在 Servlet 容器中运行时才生效。@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
:表明这个自动配置类只有在 Servlet、DispatcherServlet 和 WebMvcConfigurer 这些类都被加载时才会生效。@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
:表明这个自动配置类只有在没有 WebMvcConfigurationSupport 类型的 Bean 被注册时才会生效。@AutoConfigureOrder(-2147483638)
:表明这个自动配置类的执行顺序(优先级)为 -2147483638,即比大多数自动配置类优先级都高。
2.2.效果
1.WebMvcAutoConfiguration.javaz中放了两个Filter:
a.HiddenHttpMethodFilter
;页面表单提交Rest请求(GET、POST、PUT、DELETE)
1 |
|
这个方法旨在自动配置隐藏 HTTP 方法过滤器,它会检查 Spring 上下文中是否已经存在一个 HiddenHttpMethodFilter
类型的 Bean,如果不存在,则基于条件创建一个 OrderedHiddenHttpMethodFilter
Bean 对象。其中,OrderedHiddenHttpMethodFilter
是 Spring 提供的用于将 PUT、DELETE 等 HTTP 方法伪装成正常的 HttpMethod 的过滤器,可以在 Spring Web MVC 应用中使用。
b.FormContentFilter
: 表单内容Filter,GET(数据放URL后面)、POST(数据放请求体)请求可以携带数据,PUT、DELETE 的请求体数据会被忽略
1 |
|
这个方法旨在自动配置表单内容过滤器,它会检查 Spring 上下文中是否已经存在一个 FormContentFilter
类型的 Bean,如果不存在,则基于条件创建一个 OrderedFormContentFilter
Bean 对象。其中,OrderedFormContentFilter
是一个过滤器,用于将请求体数据转换成参数映射,方便处理表单数据。如果你没有自定义过滤器,可以直接使用此自动配置功能。
2.给容器中放了WebMvcConfigurer
组件;给SpringMVC添加各种定制功能
- 所有的功能最终会和配置文件进行绑定
- WebMvcProperties:
spring.mvc
配置文件 - WebProperties:
spring.web
配置文件
1 |
|
该类的作用是实现 Spring MVC 的自动配置,并提供一些默认的 MVC 配置。具体来说,该类包括以下几个方面的功能:
- 引入
EnableWebMvcConfiguration
类,以提供基本的 Spring MVC 配置; - 启用
WebMvcProperties
和WebProperties
配置属性类,以自动配置 Spring MVC 的一些属性; - 实现
WebMvcConfigurer
接口,以提供更多的 MVC 配置选项和定制化; - 实现
ServletContextAware
接口,以与 Servlet 上下文进行交互; - 使用
@Order(0)
注解指定此自动配置类的执行顺序为 0,即在其他自动配置类之前。
2.3. WebMvcConfigurer接口
提供了配置SpringMVC底层的所有组件入口
以下是这些回调方法的具体说明:
configurePathMatch(PathMatchConfigurer configurer)
:配置路径匹配选项;configureContentNegotiation(ContentNegotiationConfigurer configurer)
:配置内容协商选项;configureAsyncSupport(AsyncSupportConfigurer configurer)
:配置异步请求支持;configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
:配置 DefaultServlet 处理;addFormatters(FormatterRegistry registry)
:添加格式化器;addInterceptors(InterceptorRegistry registry)
:添加拦截器;addResourceHandlers(ResourceHandlerRegistry registry)
:添加静态资源处理器;addCorsMappings(CorsRegistry registry)
:添加 CORS 映射;addViewControllers(ViewControllerRegistry registry)
:添加视图控制器;configureViewResolvers(ViewResolverRegistry registry)
:配置视图解析器;addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers)
:添加自定义的参数解析器;addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers)
:添加自定义的返回值处理器;configureMessageConverters(List<HttpMessageConverter<?>> converters)
:配置消息转换器;extendMessageConverters(List<HttpMessageConverter<?>> converters)
:扩展消息转换器;configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers)
:配置异常解析器;extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers)
:扩展异常解析器;getValidator()
:获取 Validator 实例;getMessageCodesResolver()
:获取 MessageCodesResolver 实例。
2.4.静态资源规则源码
位置:WebMvcAutoConfiguration.class->WebMvcAutoConfigurationAdapter静态内部类->addResourceHandlers方法
1 |
|
- 规则一:访问:
/webjars/**
路径就去classpath:/META-INF/resources/webjars/
下找资源.- maven 导入依赖
规则二:访问:
/**
路径就去静态资源默认的四个位置找资源
- `classpath:/META-INF/resources/``
- ```classpath:/resources/`
classpath:/static/
classpath:/public/
规则三:静态资源默认都有缓存规则的设置
- 所有缓存的设置,直接通过配置文件:
spring.web
- cachePeriod: 缓存周期; 多久不用找服务器要新的。 默认没有,以s为单位
- cacheControl: HTTP缓存控制;https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching
- useLastModified:是否使用最后一次修改。配合HTTP Cache规则
- 所有缓存的设置,直接通过配置文件:
如果浏览器访问了一个静态资源
index.js
,如果服务这个资源没有发生变化,下次访问的时候就可以直接让浏览器用自己缓存中的东西,而不用给服务器发请求。
位置:addResourceHandlers方法->this.addResourceHandler方法
1 |
|
2.5.EnableWebMvcConfiguration 源码
1 |
|
如果自己手动放置了一个
WebMvcConfigurationSupport
组件,在 Spring Boot 应用程序中,将会失去 Spring Boot 自动配置的功能,因为 Spring Boot 自动配置都是依赖于WebMvcAutoConfiguration
内部的WebMvcConfigurationSupport
来实现的。因此,如果需要在 Spring Boot 中自定义 Web MVC 配置,建议是通过继承WebMvcConfigurerAdapter
或者DelegatingWebMvcConfiguration
来实现。而DelegatingWebMvcConfiguration
则更加灵活,因为它既可以使用基于 JavaConfig 的方式来配置 Spring MVC,也可以使用自动配置的方式来配置 Spring MVC。HandlerMapping
: 根据请求路径/a
找那个handler能处理请求WelcomePageHandlerMapping
:- 访问
/**
路径下的所有请求,都在以前 四个静态资源路径下找,欢迎页也一样 - 找
index.html
:只要静态资源的位置有一个index.html
页面,项目启动默认访问
- 访问
2.6. 为什们容器中放一个WebMvcConfigurer
就能配置底层行为
WebMvcAutoConfiguration
是一个自动配置类,它里面有一个EnableWebMvcConfiguration
1 |
|
EnableWebMvcConfiguration
继承与DelegatingWebMvcConfiguration
,这两个都生效
1 |
|
DelegatingWebMvcConfiguration
利用 DI 把容器中 所有WebMvcConfigurer
注入进来
1 |
|
- 别人调用
DelegatingWebMvcConfiguration
的方法配置底层规则,而它调用所有WebMvcConfigurer
的配置底层方法。
1 |
|
2.7.WebMvcConfigurationSupport
提供了很多的默认设置。
判断系统中是否有相应的类:如果有,就加入相应的HttpMessageConverter
1 |
|
3.静态资源
3.1.默认规则
3.1.1.静态资源映射
静态资源映射规则在 WebMvcAutoConfiguration
中进行了定义:
/webjars/**
的所有路径 资源都在classpath:/META-INF/resources/webjars/
/**
的所有路径 资源都在classpath:/META-INF/resources/
、classpath:/resources/
、classpath:/static/
、classpath:/public/
所有静态资源都定义了缓存规则。【浏览器访问过一次,就会缓存一段时间】,但此功能参数无默认值
period
: 缓存间隔。 默认 0S;cacheControl
:缓存控制。 默认无;useLastModified
:是否使用lastModified头。 默认 false;
3.1.2. 静态资源缓存
如前面所述
所有静态资源都定义了缓存规则。【浏览器访问过一次,就会缓存一段时间】,但此功能参数无默认值
period
: 缓存间隔。 默认 0S;cacheControl
:缓存控制。 默认无;useLastModified
:是否使用lastModified头。 默认 false;
3.1.3. 欢迎页
欢迎页规则在 WebMvcAutoConfiguration
中进行了定义:
- 在静态资源目录下找
index.html
- 没有就在
templates
下找index
模板页
3.1.4. Favicon
- 在静态资源目录下找
favicon.ico
3.1.5.缓存实验
1 |
|
3.2.自定义静态规则
自定义静态资源路径、自定义缓存规则
3.2.1.配置方式
spring.mvc
: 静态资源访问前缀路径
spring.web
:
- 静态资源目录
- 静态资源缓存策略
1 |
|
3.2.2.代码方式
容器中只要有一个 WebMvcConfigurer 组件。配置的底层行为都会生效
@EnableWebMvc //禁用boot的默认配置
方式一:
1 |
|
这段代码是定义一个Spring MVC
配置类 MyConfig
,实现了 WebMvcConfigurer
接口,并在其中重写了接口中的 addResourceHandlers
方法。在该方法中,先调用了 WebMvcConfigurer
接口的默认实现,然后添加了新的静态资源处理规则:
以
/static
开头的 URL 映射到classpath:/static/
目录下且设置缓存时间为 1180 秒
在使用 @EnableWebMvc 注解时,Spring Boot 会自动导入 WebMvcAutoConfiguration 类中的默认配置。同时,如果需要自定义配置,只需要创建一个带有 @Configuration 注解的类,实现 WebMvcConfigurer 接口,并重写其中的方法即可。
需要注意的是,在使用 @EnableWebMvc 注解开启 Spring MVC 后,默认配置将失效,因此需要手动配置所有相关的特性和组件,包括拦截器、异常处理、视图解析等等。所以,建议在不需要进行自定义配置的情况下,尽量避免使用该注解。
方式二:
1 |
|
需要注意的是,在使用自定义的 WebMvcConfigurer Bean
时,不需要使用 @EnableWebMvc
注解开启 Spring MVC 的相关配置。
4.路径匹配
pring5.3 之后加入了更多的
请求路径匹配
的实现策略;以前只支持
AntPathMatcher
策略, 现在提供了PathPatternParser
策略。并且可以让我们指定到底使用那种策略。
4.1.Ant风格路径用法
Ant 风格的路径模式语法具有以下规则:
- :表示任意数量的字符。
- ?:表示任意一个字符。
- :表示任意数量的目录。
- {}:表示一个命名的模式占位符。
- []:表示字符集合,例如[a-z]表示小写字母。
{spring:[a-z]+}
:将路径中满足[a-z]+
正则表达式的部分赋值给名为spring
的路径变量
例如:
*
:/user/*/detail
可以匹配/user/123/detail
或者/user/user_456/detail
。?
:/user/??/detail
可以匹配/user/12/detail
或者/user/ab/detail
。**
:/user/**/detail
可以匹配/user/123/detail
、/user/user_123/detail
、/user/x/y/z/detail
等多层级目录结构。{}
:/user/{id}/detail
可以匹配/user/123/detail
,并且可以通过提取id
来获取实际传入的值。[]
:/user/[a-z]/detail
可以匹配/user/a/detail
或者/user/g/detail
,但不能匹配/user/1/detail
。- {spring:[a-z]+}:
/user/{spring:[a-z]+}/detail
可以匹配/user/abc/detail
并将abc
赋值给spring
变量。注意这里必须是完全匹配才行,在 Spring MVC 中只有完全匹配才会进入 Controller 层的方法。
4.2.模式切换
AntPathMatcher 与 PathPatternParser
- PathPatternParser 在 jmh 基准测试下,有 6
8 倍吞吐量提升,降低 30%40%空间分配率- PathPatternParser 兼容 AntPathMatcher语法,并支持更多类型的路径模式
- PathPatternParser “**“ 多段匹配的支持仅允许在模式末尾使用**
1 |
|
总结:
- 使用默认的路径匹配规则,是由 PathPatternParser 提供的
- 如果路径中间需要有 **,替换成ant风格路径
1 |
|
5.内容协商
一套系统适配多端数据返回
5.1. 默认规则
基于请求头内容协商:(默认开启)
- 客户端向服务端发送请求,携带HTTP标准的Accept请求头。
- Accept:
application/json
、text/xml
、text/yaml
- 服务端根据客户端请求头期望的数据类型进行动态返回
基于请求参数内容协商:(需要开启)
- 发送请求
GET /projects/spring-boot?format=json
- 匹配到
@GetMapping("/projects/spring-boot")
- 根据参数协商,优先返回 json 类型数据【需要开启参数匹配设置】
- 发送请求
GET /projects/spring-boot?format=xml
,优先返回 xml 类型数据
- 发送请求
5.2. 效果演示
请求同一个接口,可以返回json和xml不同格式数据
- 引入支持写出xml内容依赖
1 |
|
- 标注注解
1 |
|
- 开启基于请求参数的内容协商
1 |
|
- 效果
5.3.配置协商规则与支持类型
- 修改内容协商方式
1 |
|
- 大多数 MediaType 都是开箱即用的。也可以自定义内容类型,如:
1 |
|
5.4.自定义内容返回
5.4.1.增加yaml返回支持
导入依赖
1 |
|
把对象写出成YAML
1 |
|
编写配置
1 |
|
增加HttpMessageConverter
组件,专门负责把对象写出为yaml格式
1 |
|
5.4.2. 思考:如何增加其他
配置媒体类型支持:
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
编写对应的
HttpMessageConverter
,要告诉Boot这个支持的媒体类型- 按照3的示例
把MessageConverter组件加入到底层
- 容器中放一个
WebMvcConfigurer
组件,并配置底层的MessageConverter
- 容器中放一个
5.4.3.HttpMessageConverter的示例写法
1 |
|
5.5. 内容协商原理-HttpMessageConverter
HttpMessageConverter
怎么工作?合适工作?- 定制
HttpMessageConverter
来实现多端内容协商- 编写
WebMvcConfigurer
提供的configureMessageConverters
底层,修改底层的MessageConverter
5.5.1.@ResponseBody
由HttpMessageConverter
处理
标注了
@ResponseBody
的返回值 将会由支持它的HttpMessageConverter
写给浏览器
如果controller方法的返回值标注了 @ResponseBody
注解
1.1. 请求进来先来到DispatcherServlet
的doDispatch()
进行处理
1.2. 找到一个 HandlerAdapter
适配器。利用适配器执行目标方法
1.3. RequestMappingHandlerAdapter
来执行,调用invokeHandlerMethod()
来执行目标方法
1.4. 目标方法执行之前,准备好两个东西
1.4.1. HandlerMethodArgumentResolver
:参数解析器,确定目标方法每个参数值
1.4.2. HandlerMethodReturnValueHandler
:返回值处理器,确定目标方法的返回值改怎么处理
1.5. RequestMappingHandlerAdapter
里面的invokeAndHandle()
真正执行目标方法
1.6. 目标方法执行完成,会返回返回值对象
1.7. 找到一个合适的返回值处理器 HandlerMethodReturnValueHandler
1.8. 最终找到 RequestResponseBodyMethodProcessor
能处理 标注了 @ResponseBody
注解的方法
1.9. RequestResponseBodyMethodProcessor
调用writeWithMessageConverters
,利用MessageConverter
把返回值写出去
上面解释:
@ResponseBody
由HttpMessageConverter
处理
5.5.2.HttpMessageConverter
会先进行内容协商
- 遍历所有的
MessageConverter
看谁支持这种内容类型的数据 - 默认
MessageConverter
有以下
3. 最终因为要json
所以MappingJackson2HttpMessageConverter
支持写出json
4. jackson用ObjectMapper
把对象写出去
5.5.3. WebMvcAutoConfiguration
提供几种默认HttpMessageConverters
EnableWebMvcConfiguration
通过 addDefaultHttpMessageConverters
添加了默认的MessageConverter
;如下:
ByteArrayHttpMessageConverter
: 支持字节数据读写StringHttpMessageConverter
: 支持字符串读写ResourceHttpMessageConverter
:支持资源读写ResourceRegionHttpMessageConverter
: 支持分区资源写出AllEncompassingFormHttpMessageConverter
:支持表单xml/json读写MappingJackson2HttpMessageConverter
: 支持请求响应体Json读写
默认8个:
系统提供默认的MessageConverter 功能有限,仅用于json或者普通返回数据。额外增加新的内容协商功能,必须增加新的
HttpMessageConverter
6. 模板引擎
- 由于 SpringBoot 使用了嵌入式 Servlet 容器。所以 JSP 默认是不能使用的。
- 如果需要服务端页面渲染,优先考虑使用 模板引擎。
模板引擎页面默认放在 src/main/resources/templates
SpringBoot 包含以下模板引擎的自动配置
- FreeMarker
- Groovy
- Thymeleaf
- Mustache
Thymeleaf官网:https://www.thymeleaf.org/
1 |
|
6.1. Thymeleaf整合
1 |
|
自动配置原理
- 开启了 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration 自动配置
- 属性绑定在 ThymeleafProperties 中,对应配置文件 spring.thymeleaf 内容
- 所有的模板页面默认在
classpath:/templates
文件夹下 - 默认效果
- 所有的模板页面在
classpath:/templates/
下面找 - 找后缀名为
.html
的页面
- 所有的模板页面在
6.2.基础语法
6.2.1.核心用法
th:xxx
:动态渲染指定的 html 标签属性值、或者th指令(遍历、判断等)
th:text
:标签体内文本值渲染th:utext
:不会转义,显示为html原本的样子。
th:属性
:标签指定属性渲染th:attr
:标签任意属性渲染th:if``th:each``...
:其他th指令- 例如:
1 |
|
**表达式**
:用来动态取值
${}
:变量取值;使用model共享给页面的值都直接用${}@{}
:url路径;#{}
:国际化消息~{}
:片段引用*{}
:变量选择:需要配合th:object绑定对象
系统工具&内置对象:详细文档
param
:请求参数对象session
:session对象application
:application对象#execInfo
:模板执行信息#messages
:国际化消息#uris
:uri/url工具#conversions
:类型转换工具#dates
:日期工具,是java.util.Date
对象的工具类#calendars
:类似#dates,只不过是java.util.Calendar
对象的工具类#temporals
: JDK8+**java.time**
API 工具类#numbers
:数字操作工具#strings
:字符串操作#objects
:对象操作#bools
:bool操作#arrays
:array工具#lists
:list工具#sets
:set工具#maps
:map工具#aggregates
:集合聚合工具(sum、avg)#ids
:id生成工具
6.2.2. 语法示例
表达式:
- 变量取值:${…}
- url 取值:@{…}
- 国际化消息:#{…}
- 变量选择:*{…}
- 片段引用: ~{…}
常见:
- 文本: ‘one text’,’another one!’,…
- 数字: 0,34,3.0,12.3,…
- 布尔:true、false
- null: null
- 变量名: one,sometext,main…
文本操作:
- 拼串: +
- 文本替换:| The name is ${name} |
布尔操作:
- 二进制运算: and,or
- 取反:!,not
比较运算:
- 比较:>,<,<=,>=(gt,lt,ge,le)
- 等值运算:==,!=(eq,ne)
条件运算:
- if-then: (if)?(then)
- if-then-else: (if)?(then):(else)
- default: (value)?:(defaultValue)
特殊语法:
- 无操作:_
所有以上都可以嵌套组合
1 |
|
6.3.属性设置
- th:href=”@{/product/list}”
- th:attr=”class=${active}”
- th:attr=”src=@{/images/gtvglogo.png},title=${logo},alt=#{logo}”
- th:checked=”${user.active}”
1 |
|
6.4.遍历
语法: th:each="元素名,迭代状态 : ${集合}"
1 |
|
iterStat 有以下属性:
- index:当前遍历元素的索引,从0开始
- count:当前遍历元素的索引,从1开始
- size:需要遍历元素的总数量
- current:当前正在遍历的元素对象
- even(true)/odd(false):是否偶数/奇数行
- first:是否第一个元素
- last:是否最后一个元素
6.5.判断
th:if
1 |
|
th:switch
1 |
|
6.6. 属性优先级
- 片段
- 遍历
- 判断
1 |
|
Order | Feature | Attributes |
---|---|---|
1 | 片段包含 | th:insert th:replace |
2 | 遍历 | th:each |
3 | 判断 | th:if th:unless th:switch th:case |
4 | 定义本地变量 | th:object th:with |
5 | 通用方式属性修改 | th:attr th:attrprepend th:attrappend |
6 | 指定属性修改 | th:value th:href th:src … |
7 | 文本值 | th:text th:utext |
8 | 片段指定 | th:fragment |
9 | 片段移除 | th:remove |
6.7.行内写法
[[...]] or [(...)]
1 |
|
6.8.变量选择
1 |
|
等同于
1 |
|
6.9. 模板布局
- 定义模板:
th:fragment
- 引用模板:
~{templatename::selector}
- 插入模板:
th:insert
、th:replace
1 |
|
6.10. devtools
1 |
|
修改页面后;ctrl+F9
刷新效果;
java代码的修改,如果devtools
热启动了,可能会引起一些bug,难以排查
7.国际化
国际化的自动配置参照MessageSourceAutoConfiguration
实现步骤:
- Spring Boot 在类路径根下查找messages资源绑定文件。文件名为:messages.properties
- 多语言可以定义多个消息文件,命名为messages_区域代码.properties。如:
messages.properties
:默认messages_zh_CN.properties
:中文环境messages_en_US.properties
:英语环境 - 在程序中可以自动注入 MessageSource组件,获取国际化的配置项值
- 在页面中可以使用表达式 #{}获取国际化的配置项值
1 |
|
8.错误处理
8.1.默认处理机制
错误处理的自动配置都在
ErrorMvcAutoConfiguration
中,两大核心机制:
- SpringBoot 会自适应处理错误,响应页面或JSON数据
- SpringMVC的错误处理机制依然保留,MVC处理不了,才会交给boot进行处理
发生错误以后,转发给/error路径,SpringBoot在底层写好一个 BasicErrorController的组件,专门处理这个请求
1 |
|
错误页面是这么解析到的
1 |
|
容器中专门有一个错误视图解析器
1 |
|
SpringBoot解析自定义错误页的默认规则
1 |
|
容器中有一个默认的名为 error 的 view; 提供了默认白页功能
1 |
|
规则:
解析一个错误页
如果发生了500、404、503、403 这些错误
如果有模板引擎,默认在classpath:/templates/error/精确码.html
如果没有模板引擎,在静态资源文件夹下找精确码.html
如果匹配不到
精确码.html
这些精确的错误页,就去找5xx.html
,4xx.html
模糊匹配
如果有模板引擎,默认在classpath:/templates/error/5xx.html
如果没有模板引擎,在静态资源文件夹下找5xx.html
如果模板引擎路径
templates
下有error.html
页面,就直接渲染
Springboot 系列(七)Spring Boot web 开发之异常错误处理机制剖析 - 掘金 (juejin.cn)
8.2.自定义页面响应
8.2.1. 自定义json响应
根据上面的 SpringBoot 错误处理原理分析,得知最终返回的 JSON 信息是从一个 map 对象中转换出来的,那么,只要能自定义 map 中的值,就可以自定义错误信息的 json 格式了。直接重写 DefaultErrorAttributes
类的 getErrorAttributes
方法即可。
1 |
|
测试返回结果:
1 |
|
8.2.2.自定义页面响应
根据boot的错误页面规则,自定义页面模板
8.3.实战
处理系统异常
1 |
|
拦截自定义异常
定义异常信息
由于在业务中,有很多异常,针对不同的业务,可能给出的提示信息不同,所以为了方便项目异常信息管理,我们一般会定义一个异常信息枚举类。比如:
1 |
|
拦截自定义异常
然后我们可以定义一个业务异常,当出现业务异常时,我们就抛这个自定义的业务异常即可。比如我们定义一个 BusinessErrorException 异常,如下:
1 |
|
在构造方法中,传入我们上面自定义的异常枚举类,所以在项目中,如果有新的异常信息需要添加,我们直接在枚举类中添加即可,很方便,做到统一维护,然后再拦截该异常时获取即可。
1 |
|
在业务代码中,我们可以直接模拟一下抛出业务异常,测试一下:
1 |
|
运行一下项目,测试一下,返回 json 如下,说明我们自定义的业务异常捕获成功:
1 |
|
6.8.4.总结
前后分离
- 后台发生的所有错误,
@ControllerAdvice + @ExceptionHandler
进行统一异常处理。
- 后台发生的所有错误,
服务端页面渲染
不可预知的一些,HTTP码表示的服务器或客户端错误
- 给
classpath:/templates/error/
下面,放常用精确的错误码页面。500.html
,404.html
- 给
classpath:/templates/error/
下面,放通用模糊匹配的错误码页面。5xx.html
,4xx.html
- 给
发生业务错误
- 核心业务,每一种错误,都应该代码控制,跳转到自己定制的错误页。
- 通用业务,
classpath:/templates/error.html
页面,显示错误信息。
页面,JSON,可用的Model数据如下
9.嵌入式容器
Servlet容器:管理、运行Servlet组件(Servlet、Filter、Listener)的环境,一般指服务器
9.1. 自动配置原理
- SpringBoot 默认嵌入Tomcat作为Servlet容器。
- 自动配置类是
ServletWebServerFactoryAutoConfiguration
,EmbeddedWebServerFactoryCustomizerAutoConfiguration
- 自动配置类开始分析功能。
xxxxAutoConfiguration
1 |
|
ServletWebServerFactoryAutoConfiguration
自动配置了嵌入式容器场景绑定了
ServerProperties
配置类,所有和服务器有关的配置server
ServletWebServerFactoryAutoConfiguration
导入了 嵌入式的三大服务器Tomcat
、Jetty
、Undertow
- 导入
Tomcat
、Jetty
、Undertow
都有条件注解。系统中有这个类才行(也就是导了包) - 默认
Tomcat
配置生效。给容器中放TomcatServletWebServerFactory
- 都给容器中
ServletWebServerFactory
放了一个 web服务器工厂(造web服务器的) - web服务器工厂 都有一个功能,
getWebServer
获取web服务器 - TomcatServletWebServerFactory 创建了 tomcat。
- 导入
ServletWebServerFactory 什么时候会创建 webServer出来。
ServletWebServerApplicationContext
ioc容器,启动的时候会调用创建web服务器Spring容器刷新(启动)的时候,会预留一个时机,刷新子容器。
onRefresh()
refresh() 容器刷新 十二大步的刷新子容器会调用
onRefresh()
;
1 |
|
Web场景的Spring容器启动,在onRefresh的时候,会调用创建web服务器的方法。
Web服务器的创建是通过WebServerFactory搞定的。容器中又会根据导了什么包条件注解,启动相关的 服务器配置,默认
EmbeddedTomcat
会给容器中放一个TomcatServletWebServerFactory
,导致项目启动,自动创建出Tomcat。
9.2. 自定义
切换服务器;
1 |
|
9.3. 最佳实践
用法:
- 修改
server
下的相关配置就可以修改服务器参数 - 通过给容器中放一个
ServletWebServerFactory
,来禁用掉SpringBoot默认放的服务器工厂,实现自定义嵌入任意服务器。
10.全面接管SpringMVC
SpringBoot 默认配置好了 SpringMVC 的所有常用特性。
如果我们需要全面接管SpringMVC的所有配置并禁用默认配置,仅需要编写一个WebMvcConfigurer
配置类,并标注@EnableWebMvc
即可。
全手动模式
@EnableWebMvc
: 禁用默认配置WebMvcConfigurer
组件:定义MVC的底层行为
10.1. WebMvcAutoConfiguration 到底自动配置了哪些规则
SpringMVC自动配置场景给我们配置了如下所有默认行为
1.WebMvcAutoConfigurationweb场景的自动配置类
- 1.1.支持RESTful的filter:HiddenHttpMethodFilter
- 1.2.支持非POST请求,请求体携带数据:FormContentFilter
- 1.3.导入
EnableWebMvcConfiguration
: - 1.3.1.RequestMappingHandlerAdapter
- 1.3.2.WelcomePageHandlerMapping
: 欢迎页功能支持(模板引擎目录、静态资源目录放index.html),项目访问/ (根目录)就默认展示这个页面. - 1.3.3.RequestMappingHandlerMapping
:找每个请求由谁处理的映射关系 - 1.3.4.ExceptionHandlerExceptionResolver
:默认的异常解析器 - 1.3.5.LocaleResolver
:国际化解析器 - 1.3.6.ThemeResolver
:主题解析器 - 1.3.7.FlashMapManager
:临时数据共享 - 1.3.8.FormattingConversionService
: 数据格式化 、类型转化 - 1.3.9.Validator
: 数据校验JSR303提供的数据校验功能 - 1.3.10.WebBindingInitializer
:请求参数的封装与绑定 - 1.3.11.ContentNegotiationManager
:内容协商管理器 - 1.4.
WebMvcAutoConfigurationAdapter
配置生效,它是一个WebMvcConfigurer
,定义mvc底层组件 - 1.4.1.定义好WebMvcConfigurer
底层组件默认功能;所有功能详见下列表 - 1.4.2.视图解析器:InternalResourceViewResolver
- 1.4.3.视图解析器:BeanNameViewResolver
,视图名(controller方法的返回值字符串)就是组件名 - 1.4.4.内容协商解析器:ContentNegotiatingViewResolver
- 1.4.5.请求上下文过滤器:RequestContextFilter
: 任意位置直接获取当前请求 - 1.4.6.静态资源链规则 - 1.4.7.ProblemDetailsExceptionHandler
:错误详情- 1.4.7.1.`SpringMVC`内部场景异常被它捕获:
- 1.5.定义了MVC默认的底层行为:
WebMvcConfigurer
10.2. @EnableWebMvc 禁用默认行为
@EnableWebMvc
给容器中导入DelegatingWebMvcConfiguration
组件,他是WebMvcConfigurationSupport
WebMvcAutoConfiguration
有一个核心的条件注解,@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
,容器中没有WebMvcConfigurationSupport
,WebMvcAutoConfiguration
才生效.@EnableWebMvc
导入WebMvcConfigurationSupport
导致WebMvcAutoConfiguration
失效。导致禁用了默认行为
- @EnableWebMVC 禁用了 MVC 的自动配置
- WebMvcConfigurer 定义SpringMVC底层组件的功能类
10.3. WebMvcConfigurer 功能
定义扩展SpringMVC底层功能
提供方法 | 核心参数 | 功能 | 默认 |
---|---|---|---|
addFormatters | FormatterRegistry | 格式化器:支持属性上@NumberFormat和@DatetimeFormat的数据类型转换 | GenericConversionService |
getValidator | 无 | 数据校验:校验 Controller 上使用@Valid标注的参数合法性。需要导入starter-validator | 无 |
addInterceptors | InterceptorRegistry | 拦截器:拦截收到的所有请求 | 无 |
configureContentNegotiation | ContentNegotiationConfigurer | 内容协商:支持多种数据格式返回。需要配合支持这种类型的HttpMessageConverter | 支持 json |
configureMessageConverters | List<HttpMessageConverter<?>> | 消息转换器:标注@ResponseBody的返回值会利用MessageConverter直接写出去 | 8 个,支持byte,string,multipart,resource,json |
addViewControllers | ViewControllerRegistry | 视图映射:直接将请求路径与物理视图映射。用于无 java 业务逻辑的直接视图页渲染 | 无 <mvc:view-controller> |
configureViewResolvers | ViewResolverRegistry | 视图解析器:逻辑视图转为物理视图 | ViewResolverComposite |
addResourceHandlers | ResourceHandlerRegistry | 静态资源处理:静态资源路径映射、缓存控制 | ResourceHandlerRegistry |
configureDefaultServletHandling | DefaultServletHandlerConfigurer | 默认 Servlet:可以覆盖 Tomcat 的DefaultServlet。让DispatcherServlet拦截/ | 无 |
configurePathMatch | PathMatchConfigurer | 路径匹配:自定义 URL 路径匹配。可以自动为所有路径加上指定前缀,比如 /api | 无 |
configureAsyncSupport | AsyncSupportConfigurer | 异步支持: | TaskExecutionAutoConfiguration |
addCorsMappings | CorsRegistry | 跨域: | 无 |
addArgumentResolvers | List<HandlerMethodArgumentResolver> | 参数解析器: | mvc 默认提供 |
addReturnValueHandlers | List<HandlerMethodReturnValueHandler> | 返回值解析器: | mvc 默认提供 |
configureHandlerExceptionResolvers | List<HandlerExceptionResolver> | 异常处理器: | 默认 3 个 ExceptionHandlerExceptionResolver ResponseStatusExceptionResolver DefaultHandlerExceptionResolver |
getMessageCodesResolver | 无 | 消息码解析器:国际化使用 | 无 |
10.4. 最佳实践
SpringBoot 已经默认配置好了Web开发场景常用功能。我们直接使用即可。
三种方式
方式 | 用法 | 效果 | |
---|---|---|---|
全自动 | 直接编写控制器逻辑 | 全部使用自动配置默认效果 | |
手自一体 | @Configuration + 配置WebMvcConfigurer + 配置 WebMvcRegistrations |
不要标注 @EnableWebMvc |
保留自动配置效果 手动设置部分功能 定义MVC底层组件 |
全手动 | @Configuration + 配置WebMvcConfigurer |
标注 @EnableWebMvc |
禁用自动配置效果 全手动设置 |
总结:
给容器中写一个配置类@Configuration
实现 WebMvcConfigurer
但是不要标注 @EnableWebMvc
注解,实现手自一体的效果。
两种模式
1、前后分离模式
: @RestController
响应JSON数据
2、前后不分离模式
:@Controller + Thymeleaf模板引擎
11.web新特性
11.1. Problemdetails
RFC 7807: https://www.rfc-editor.org/rfc/rfc7807
错误信息返回新格式
原理
1 |
|
ProblemDetailsExceptionHandler
是一个@ControllerAdvice
集中处理系统异常- 处理以下异常。如果系统出现以下异常,会被SpringBoot支持以
RFC 7807
规范方式返回错误数据
1 |
|
效果:
默认响应错误的json。状态码 405
1 |
|
开启ProblemDetails返回, 使用新的MediaType
1 |
|
Content-Type: application/problem+json
+ 额外扩展返回
1 |
|
11.2.函数式Web
SpringMVC 5.2
以后 允许我们使用函数式的方式,定义Web的请求处理流程。函数式接口
Web请求处理的方式:
@Controller + @RequestMapping
:耦合式 (路由、业务耦合)- 函数式Web:分离式(路由、业务分离)
11.2.1.场景
场景:User RESTful - CRUD
- GET /user/1 获取1号用户
- GET /users 获取所有用户
- POST /user 请求体携带JSON,新增一个用户
- PUT /user/1 请求体携带JSON,修改1号用户
- DELETE /user/1 删除1号用户
11.2.2. 核心类
- RouterFunction : 定义路由信息。发送什么请求,谁来处理
- RequestPredicate : 定义请求谓语。请求方式(GET、POST)、请求参数
- ServerRequest : 封装请求完整数据
- ServerResponse : 封装响应完整数据
11.2.3. 示例
1 |
|
1 |
|
三、数据访问
1.整合SSM场景
SpringBoot 整合
Spring
、SpringMVC
、MyBatis
进行数据访问场景开发
1.1.创建SSM整合项目
1 |
|
1.2.配置数据源
1 |
|
安装MyBatisX 插件,帮我们生成Mapper接口的xml文件即可
1.3. 配置MyBatis
1 |
|
1.4. CRUD编写
- 编写Bean
- 编写Mapper
- 使用
mybatisx
插件,快速生成MapperXML - 测试CRUD
1.5. 自动配置原理
SSM整合总结:
导入
mybatis-spring-boot-starter
配置数据源信息
配置mybatis的
**mapper接口扫描**
与**xml映射文件扫描**
编写bean,mapper,生成xml,编写sql 进行crud。事务等操作依然和Spring中用法一样
效果:
- 所有sql写在xml中
- 所有
mybatis配置
写在application.properties
下面
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
- 支持事务
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- 具有的底层能力:数据源、
JdbcTemplate
、事务
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接口类,创建Bean定义信息,注册到容器中。
- 利用
如何分析哪个场景导入以后,开启了哪些自动配置类。
找:classpath:/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中配置的所有值,就是要开启的自动配置类,但是每个类可能有条件注解,基于条件注解判断哪个自动配置类生效了。
1.6. 快速定位生效的配置
1 |
|
1.7. 扩展:整合其他数据源
Druid 数据源:
暂不支持
SpringBoot3
- 导入
druid-starter
- 写配置
- 分析自动配置了哪些东西,怎么用
Druid官网:https://github.com/alibaba/druid
1 |
|
1.8.附录
1 |
|
四、SpringBoot3-基础特性
1.SpringApplication
1.1.自定义 banner
- 类路径添加banner.txt或设置spring.banner.location就可以定制 banner
- 推荐网站:Spring Boot banner 在线生成工具,制作下载英文 banner.txt,修改替换 banner.txt 文字实现自定义,个性化启动 banner-bootschool.net
1.2.自定义 SpringApplication
1 |
|
1.3. FluentBuilder API
1 |
|
2. Profiles
环境隔离能力;快速切换开发、测试、生产环境
步骤:
- 标识环境:指定哪些组件、配置在哪个环境生效
- 切换环境:这个环境对应的所有组件和配置就应该生效
2.1. 使用
2.1.1 指定环境
- Spring Profiles 提供一种隔离配置的方式,使其仅在特定环境生效;
- 任何@Component, @Configuration 或 @ConfigurationProperties 可以使用 @Profile 标记,来指定何时被加载。【容器中的组件都可以被
@Profile
标记】
2.1.2 环境激活
- 配置激活指定环境; 配置文件
1 |
|
- 也可以使用命令行激活。–spring.profiles.active=dev,hsqldb
- 还可以配置默认环境; 不标注@Profile 的组件永远都存在。
- 以前默认环境叫default
spring.profiles.default=test
- 推荐使用激活方式激活指定环境
2.1.3 环境包含
不管激活那个环境,这个都得有。总是要生效的环境
注意:
- spring.profiles.active 和spring.profiles.default 只能用到 无 profile 的文件中,如果在application-dev.yaml中编写就是无效的
- 也可以额外添加生效文件,而不是激活替换。比如:
1 |
|
最佳实战:
- 生效的环境 = 激活的环境/默认环境 + 包含的环境
- 项目里面这么用
- 基础的配置
mybatis
、log
、xxx
:写到包含环境中 - 需要动态切换变化的
db
、redis
:写到激活的环境中
- 基础的配置
2.2. Profile 分组
创建prod组,指定包含db和mq配置
1 |
|
使用–spring.profiles.active=prod ,就会激活prod,db,mq配置文件
2.3. Profile 配置文件
application-{profile}.properties
可以作为指定环境的配置文件。- 激活这个环境,配置就会生效。最终生效的所有配置是
application.properties
:主配置文件,任意时候都生效application-{profile}.properties
:指定环境配置文件,激活指定环境生效
profile优先级 > application
2.4.YML分段配置示例
1 |
|
3.外部化配置
场景:线上应用如何快速修改配置,并应用最新配置?
- SpringBoot 使用 配置优先级 + 外部配置 简化配置更新、简化运维。
- 只需要给
jar
应用所在的文件夹放一个application.properties
最新配置文件,重启项目就能自动应用最新配置
3.1. 配置优先级
Spring Boot 允许将配置外部化,以便可以在不同的环境中使用相同的应用程序代码。
我们可以使用各种外部配置源,包括Java Properties文件、YAML文件、环境变量和命令行参数。
@Value可以获取值,也可以用@ConfigurationProperties将所有属性绑定到java object中
以下是 SpringBoot 属性源加载顺序。后面的会覆盖前面的值**。由低到高,高优先级配置覆盖低优先级
- 默认属性(通过
SpringApplication.setDefaultProperties
指定的) - @PropertySource指定加载的配置(需要写在@Configuration类上才可生效)
- 配置文件(application.properties/yml等)
- RandomValuePropertySource支持的random.*配置(如:@Value(“${random.int}”))
- OS 环境变量
- Java 系统属性(System.getProperties())
- JNDI 属性(来自java:comp/env)
- ServletContext 初始化参数
- ServletConfig 初始化参数
- SPRING_APPLICATION_JSON属性(内置在环境变量或系统属性中的 JSON)
- 命令行参数
- 测试属性。(@SpringBootTest进行测试时指定的属性)
- 测试类@TestPropertySource注解
- Devtools 设置的全局属性。($HOME/.config/spring-boot)
结论:配置可以写到很多位置,常见的优先级顺序:
命令行
>配置文件
>springapplication配置
配置文件优先级如下:(后面覆盖前面)
- jar 包内的application.properties/yml
- jar 包内的application-{profile}.properties/yml
- jar 包外的application.properties/yml
- jar 包外的application-{profile}.properties/yml
建议:用一种格式的配置文件。如果.properties和.yml同时存在,则.properties优先
结论:包外 > 包内
; 同级情况:profile配置 > application配置
所有参数均可由命令行传入,使用--参数项=参数值
,将会被添加到环境变量中,并优先于配置文件
。
比如java -jar app.jar --name="Spring"
,可以使用@Value("${name}")
获取
演示场景:
- 包内: application.properties
server.port=8000
- 包内: application-dev.properties
server.port=9000
- 包外: application.properties
server.port=8001
- 包外: application-dev.properties
server.port=9001
启动端口?:命令行 > 9001
> 8001
> 9000
> 8000
3.2. 外部配置
SpringBoot 应用启动时会自动寻找application.properties和application.yaml位置,进行加载。顺序如下:(后面覆盖前面)
类路径: 内部
- 类根路径
- 类下/config包
当前路径(项目所在的位置)
- 当前路径
- 当前下/config子目录
- /config目录的直接子目录
最终效果:优先级由高到低,前面覆盖后面
- 命令行 > 包外config直接子目录 > 包外config目录 > 包外根目录 > 包内目录
- 同级比较:
- profile配置 > 默认配置
- properties配置 > yaml配置
规律:最外层的最优先。
- 命令行 > 所有
- 包外 > 包内
- config目录 > 根目录
- profile > application
配置不同就都生效(互补),配置相同高优先级覆盖低优先级
3.3. 导入配置
使用spring.config.import可以导入额外配置
1 |
|
无论以上写法的先后顺序,my.properties的值总是优先于直接在文件中编写的my.property。
3.4. 属性占位符
配置文件中可以使用 ${name:default}形式取出之前配置过的值。
1 |
|
4.单元测试-JUnit5
4.1. 整合
SpringBoot 提供一系列测试工具集及注解方便我们进行测试。
spring-boot-test
提供核心测试能力,spring-boot-test-autoconfigure
提供测试的一些自动配置。
我们只需要导入spring-boot-starter-test
即可整合测试
1 |
|
spring-boot-starter-test
默认提供了以下库供我们测试使用
4.2. 测试
4.2.1 组件测试
直接@Autowired容器中的组件进行测试
4.2.2 注解
JUnit5的注解与JUnit4的注解有所变化
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
- @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
- @RepeatedTest :表示方法可重复执行,下方会有详细介绍
- @DisplayName :为测试类或者测试方法设置展示名称
- @BeforeEach :表示在每个单元测试之前执行
- @AfterEach :表示在每个单元测试之后执行
- @BeforeAll :表示在所有单元测试之前执行
- @AfterAll :表示在所有单元测试之后执行
- @Tag :表示单元测试类别,类似于JUnit4中的@Categories
- @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
- @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith :为测试类或测试方法提供扩展类引用
1 |
|
4.2.3.断言
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
assertArrayEquals | 数组断言 |
assertAll | 组合断言 |
assertThrows | 异常断言 |
assertTimeout | 超时断言 |
fail | 快速失败 |
4.2.4.嵌套测试
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。
1 |
|
4.2.5.参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
1 |
|
五、SpringBoot3-核心原理
1.事件和监听器
1.1生命周期监听
场景:监听应用的生命周期
1.1.1.监听器-SpringApplicationRunListener
自定义SpringApplicationRunListener
来监听事件:
- 编写
SpringApplicationRunListener
实现类 - 在
META-INF/spring.factories
中配置org.springframework.boot.SpringApplicationRunListener=自己的Listener
,还可以指定一个有参构造器,接受两个参数(SpringApplication application, String[] args)
- springboot 在
spring-boot.jar
中配置了默认的 Listener,如下:
Listener先要从META-INF/spring.factories
读到
- 1、引导: 利用
BootstrapContext
引导整个项目启动- starting:应用开始,
SpringApplication
的run
方法一调用,只要有了BootstrapContext
就执行 - environmentPrepared: 环境准备好(把启动参数等绑定到环境变量中),但是
ioc
还没有创建;【调一次】
- starting:应用开始,
- 2、启动:
- contextPrepared:
ioc
容器创建并准备好,但是sources
(主配置类)没加载。并关闭引导上下文;组件都没创建 【调一次】 - contextLoaded:
ioc
容器加载。主配置类加载进去了。但是ioc
容器还没刷新(我们的bean没创建)。 - =======截止以前,
ioc
容器里面还没造bean
呢======= - started:
ioc
容器刷新了(所有bean造好了),但是runner
没调用。 - ready:
ioc
容器刷新了(所有bean造好了),所有runner
调用完了。
- contextPrepared:
- 3、运行
- 以前步骤都正确执行,代表容器running。
1.1.2.生命周期全流程
1.1.3.测试代码
- 编写java代码实现SpringApplicationRunListener接口并重写接口方法:
1 |
|
- 编写rsources/META-INF/spring.factories文件
1 |
|
1.2.事件触发时机
1.2.1.各种回调监听器
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
1.2.2.完整触发流程
9大事件
触发顺序&时机
ApplicationStartingEvent
:应用启动但未做任何事情, 除过注册listeners and initializers.ApplicationEnvironmentPreparedEvent
: Environment 准备好,但context 未创建.ApplicationContextInitializedEvent
: ApplicationContext 准备好,ApplicationContextInitializers 调用,但是任何bean未加载ApplicationPreparedEvent
: 容器刷新之前,bean定义信息加载ApplicationStartedEvent
: 容器刷新完成, runner未调用
=========以下就开始插入了探针机制============AvailabilityChangeEvent
:LivenessState.CORRECT
应用存活; 存活探针ApplicationReadyEvent
: 任何runner被调用AvailabilityChangeEvent
:ReadinessState.ACCEPTING_TRAFFIC
就绪探针,可以接请求-
ApplicationFailedEvent
:启动出错
应用事件发送顺序如下:
感知应用是否存活了:可能植物状态,虽然活着但是不能处理请求。
应用是否就绪了:能响应请求,说明确实活的比较好。
1.2.3.SpringBoot 事件驱动开发
应用启动过程生命周期事件感知(9大事件)、应用运行中事件感知(无数种)。
- 事件发布:
ApplicationEventPublisherAware
或注入:ApplicationEventMulticaster
- 事件监听:
组件 + @EventListener
- 事件发布者
1 |
|
- 自定义事件
1 |
|
- 事件订阅者
1 |
|
或
1 |
|
- controller
1 |
|
2.自动配置原理
2.1.入门理解
应用关注的三大核心:场景、配置、组件
2.1.1.自动配置流程
- 导入
starter
- 依赖导入
autoconfigure
- 寻找类路径下
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件 - 启动,加载所有
自动配置类
xxxAutoConfiguration
- 给容器中配置功能
组件
组件参数
绑定到属性类
中。xxxProperties
属性类
和配置文件
前缀项绑定@Contional派生的条件注解
进行判断是否组件生效
- 给容器中配置功能
- 效果:
- 修改配置文件,修改底层参数
- 所有场景自动配置好直接使用
- 可以注入SpringBoot配置好的组件随时使用
2.1.2.SPI机制
- Java中的SPI(Service Provider Interface)是一种软件设计模式,用于在应用程序中动态地发现和加载组件。SPI的思想是,定义一个接口或抽象类,然后通过在classpath中定义实现该接口的类来实现对组件的动态发现和加载。
- SPI的主要目的是解决在应用程序中使用可插拔组件的问题。例如,一个应用程序可能需要使用不同的日志框架或数据库连接池,但是这些组件的选择可能取决于运行时的条件。通过使用SPI,应用程序可以在运行时发现并加载适当的组件,而无需在代码中硬编码这些组件的实现类。
- 在Java中,SPI的实现方式是通过在
META-INF/services
目录下创建一个以服务接口全限定名为名字的文件,文件中包含实现该服务接口的类的全限定名。当应用程序启动时,Java的SPI机制会自动扫描classpath中的这些文件,并根据文件中指定的类名来加载实现类。- 通过使用SPI,应用程序可以实现更灵活、可扩展的架构,同时也可以避免硬编码依赖关系和增加代码的可维护性。
以上回答来自
ChatGPT-3.5
在SpringBoot中,META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
2.1.3.功能开关
自动配置:全部都配置好,什么都不用管。 自动批量导入
- 项目一启动,spi文件中指定的所有都加载。
@EnableXxxx
:手动控制哪些功能的开启; 手动导入。- 开启xxx功能
- 都是利用 @Import 把此功能要用的组件导入进去
2.2.进阶理解
2.2.1.@SpringBootApplication
@SpringBootConfiguration
就是: @Configuration
,容器中的组件,配置类。spring ioc启动就会加载创建这个类对象
@EnableAutoConfiguration:开启自动配置
开启自动配置
@AutoConfigurationPackage:扫描主程序包:加载自己的组件
- 利用
@Import(AutoConfigurationPackages.Registrar.class)
想要给容器中导入组件。 - 把主程序所在的包的所有组件导入进来。
- 为什么SpringBoot默认只扫描主程序所在的包及其子包
@Import(AutoConfigurationImportSelector.class):加载所有自动配置类:加载starter导入的组件
1 |
|
扫描SPI文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@ComponentScan
组件扫描:排除一些组件(哪些不要)
排除前面已经扫描进来的配置类
、和自动配置类
。
1 |
|
2.2.2.完整启动加载流程
3.自定义starter
场景:抽取聊天机器人场景,它可以打招呼。
效果:任何项目导入此
starter
都具有打招呼功能,并且问候语中的人名需要可以在配置文件中修改
- 创建
自定义starter
项目,引入spring-boot-starter
基础依赖 - 编写模块功能,引入模块所有需要的依赖。
- 编写
xxxAutoConfiguration
自动配置类,帮其他项目导入这个模块需要的所有组件 - 编写配置文件
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
指定启动需要加载的自动配置 - 其他项目引入即可使用
3.1. 业务代码
自定义配置有提示。导入以下依赖重启项目,再写配置文件就有提示
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
3.2.基本抽取
创建starter项目,把公共代码需要的所有依赖导入
把公共代码复制进来
自己写一个
RobotAutoConfiguration
,给容器中导入这个场景需要的所有组件- 为什么这些组件默认不会扫描进去?
- starter所在的包和 引入它的项目的主程序所在的包不是父子层级
别人引用这个
starter
,直接导入这个RobotAutoConfiguration
,就能把这个场景的组件导入进来功能生效。
测试编写配置文件
3.3. 使用@EnableXxx机制
1 |
|
3.4. 完全自动配置
- 依赖SpringBoot的SPI机制
- META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中编写好我们自动配置类的全类名即可
- 项目启动,自动加载我们的自动配置类
org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件内容:
1 |
|
六、附录:SpringBoot3改变 & 新特性 快速总结
1、自动配置包位置变化【参照视频:07、11】
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
2、jakata api迁移
- druid有问题
3、新特性 - 函数式Web、ProblemDetails【参照视频:50、51】
4、GraalVM 与 AOT【参照视频:86~93】
5、响应式编程全套【第三季:预计7~8月份发布】
6、剩下变化都是版本升级,意义不大
七、springboot3-场景整合
1.环境准备
1.1. 云服务器
1.2.docker安装
还不会docker的同学,参考【云原生实战(10~25集)】快速入门
1 |
|
创建 /prod
文件夹,准备以下文件
1.3.prometheus.yml
1 |
|
1.4.docker-compose.yml
1 |
|
1.5.启动环境
1 |
|
1.6.验证
Redis:你的ip:6379
- 填写表单,下载官方可视化工具:
- https://redis.com/redis-enterprise/redis-insight/#insight-form
Kafka:你的ip:9092
- idea安装大数据插件
Prometheus:你的ip:9090
- 直接浏览器访问
Grafana:你的ip:3000
- 直接浏览器访问
2.redis整合
Redis不会的同学:参照 阳哥-《Redis7》 https://www.bilibili.com/video/BV13R4y1v7sP?p=1
HashMap: key:value
2.1.场景整合
依赖导入
1 |
|
配置
1 |
|
测试
1 |
|
2.2.自动配置原理
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
中导入了RedisAutoConfiguration
、RedisReactiveAutoConfiguration
和RedisRepositoriesAutoConfiguration
所有属性绑定在RedisProperties
中RedisReactiveAutoConfiguratio
属于响应式编程,不用管。RedisRepositoriesAutoConfiguration
属于 JPA 操作,也不用管RedisAutoConfiguration
配置了以下组件LettuceConnectionConfiguration
: 给容器中注入了连接工厂LettuceConnectionFactory
,和操作 redis 的客户端DefaultClientResources
。RedisTemplate<Object, Object>
: 可给 redis 中存储任意对象,会使用 jdk 默认序列化方式。StringRedisTemplate
: 给 redis 中存储字符串,如果要存对象,需要开发人员自己进行序列化。key-value都是字符串进行操作··
2.3.定制化
2.3.1.序列化机制
1 |
|
2.3.2.redis客户端
RedisTemplate、StringRedisTemplate: 操作redis的工具类
要从redis的连接工厂获取链接才能操作redis
Redis客户端
- Lettuce: 默认
- Jedis:可以使用以下切换
1 |
|
2.3.3. 配置参考
1 |
|
3.接口文档
Swagger 可以快速生成实时接口文档,方便前后开发人员进行协调沟通。遵循 OpenAPI 规范。
3.1. OpenAPI 3 架构
3.2.整合
导入场景
1 |
|
或者导入
1 |
|
配置
1 |
|
3.3.使用
3.3.1.常用注解
注解 | 标注位置 | 作用 |
---|---|---|
@Tag | controller 类 | 标识 controller 作用 |
@Parameter | 参数 | 标识参数作用 |
@Parameters | 参数 | 参数多重说明 |
@Schema | model 层的 JavaBean | 描述模型作用及每个属性 |
@Operation | 方法 | 描述方法作用 |
@ApiResponse | 方法 | 描述响应状态码等 |
3.3.2.Docket配置
如果有多个Docket,配置如下
1 |
|
如果只有一个Docket,可以配置如下
1 |
|
3.3.3.OpenAPI配置
1 |
|
3.4.Springfox 迁移
3.4.1.注解变化
原注解 | 现注解 | 作用 |
---|---|---|
@Api | @Tag | 描述Controller |
@ApiIgnore | @Parameter(hidden = true) @Operation(hidden = true) @Hidden | 描述忽略操作 |
@ApiImplicitParam | @Parameter | 描述参数 |
@ApiImplicitParams | @Parameters | 描述参数 |
@ApiModel | @Schema | 描述对象 |
@ApiModelProperty(hidden = true) | @Schema(accessMode = READ_ONLY) | 描述对象属性 |
@ApiModelProperty | @Schema | 描述对象属性 |
@ApiOperation(value = “foo”, notes = “bar”) | @Operation(summary = “foo”, description = “bar”) | 描述方法 |
@ApiParam | @Parameter | 描述参数 |
@ApiResponse(code = 404, message = “foo”) | @ApiResponse(responseCode = “404”, description = “foo”) | 描述响应 |
3.4.2.Docket配置
以前写法
1 |
|
新写法
1 |
|
添加OpenAPI组件
1 |
|
4.远程调用
RPC(Remote Procedure Call):远程过程调用
本地过程调用: a(); b(); a() { b();}: 不同方法都在同一个JVM运行
远程过程调用:
- 服务提供者:
- 服务消费者:
- 通过连接对方服务器进行请求\响应交互,来实现调用效果
API/SDK的区别是什么?
- api:接口(Application Programming Interface)
- 远程提供功能;
- sdk:工具包(Software Development Kit)
- 导入jar包,直接调用功能即可
开发过程中,我们经常需要调用别人写的功能
- 如果是内部微服务,可以通过依赖cloud、注册中心、openfeign等进行调用
- 如果是外部暴露的,可以发送 http 请求、或遵循外部协议进行调用
SpringBoot 整合提供了很多方式进行远程调用
- 轻量级客户端方式
- RestTemplate: 普通开发
- WebClient: 响应式编程开发
- Http Interface: 声明式编程
- Spring Cloud分布式解决方案方式
- Spring Cloud OpenFeign
- 第三方框架
- Dubbo
- gRPC
- …
4.1.WebClient
4.4.1.创建与配置
发请求:
- 请求方式: GET\POST\DELETE\xxxx
- 请求路径: /xxx
- 请求参数:aa=bb&cc=dd&xxx
- 请求头: aa=bb,cc=ddd
- 请求体:
创建 WebClient 非常简单:
- WebClient.create()
- WebClient.create(String baseUrl)
还可以使用 WebClient.builder() 配置更多参数项:
- uriBuilderFactory: 自定义UriBuilderFactory ,定义 baseurl.
- defaultUriVariables: 默认 uri 变量.
- defaultHeader: 每个请求默认头.
- defaultCookie: 每个请求默认 cookie.
- defaultRequest: Consumer 自定义每个请求.
- filter: 过滤 client 发送的每个请求
- exchangeStrategies: HTTP 消息 reader/writer 自定义.
- clientConnector: HTTP client 库设置.
1 |
|
4.1.2 获取响应
retrieve()方法用来声明如何提取响应数据。比如
1 |
|
4.1.3.定义请求体
1 |
|
4.2.HTTP Interface
Spring 允许我们通过定义接口的方式,给任意位置发送 http 请求,实现远程调用,可以用来简化 HTTP 远程访问。需要webflux场景才可使用。
4.2.1 导入依赖
1 |
|
4.2.2 定义接口
1 |
|
4.2.3 创建代理&测试
1 |
|
5.消息服务
5.1.消息队列-场景
5.1.1. 异步
5.1.2. 解耦
5.1.3. 削峰
5.1.4.4. 缓冲
5.2消息队列-Kafka
5.2.1.消息模式
5.2.2.Kafka工作原理
5.2.3. SpringBoot整合
参照:https://docs.spring.io/spring-kafka/docs/current/reference/html/#preface
1 |
|
配置
1 |
|
修改C:\Windows\System32\drivers\etc\hosts
文件,配置8.130.32.70 kafka
5.2.4.消息发送
1 |
|
1 |
|
5.2.5.消息监听
1 |
|
5.2.6.参数配置
消费者
1 |
|
生产者
1 |
|
5.2.7.自动配置原理
kafka 自动配置在KafkaAutoConfiguration
- 容器中放了 KafkaTemplate 可以进行消息收发
- 容器中放了KafkaAdmin 可以进行 Kafka 的管理,比如创建 topic 等
- kafka 的配置在KafkaProperties中
- @EnableKafka可以开启基于注解的模式
6.web安全
- Apache Shiro
- Spring Security
- 自研:Filter
6.1.Spring Security架构
6.1.1.认证:Authentication
who are you?
登录系统,用户系统
6.1.2. 授权:Authorization
what are you allowed to do?
权限管理,用户授权
6.1.3.攻击防护
- XSS(Cross-site scripting)
- CSRF(Cross-site request forgery)
- CORS(Cross-Origin Resource Sharing)
- SQL注入
- …
6.1.4.扩展. 权限模型
RBAC(Role Based Access Controll)
用户(t_user)
- id,username,password,xxx
- 1,zhangsan
- 2,lisi
用户_角色(t_user_role)【N对N关系需要中间表】
- zhangsan, admin
- zhangsan,common_user
- lisi, hr
- lisi, common_user
角色(t_role)
- id,role_name
- admin
- hr
- common_user
角色_权限(t_role_perm)
- admin, 文件r
- admin, 文件w
- admin, 文件执行
- admin, 订单query,create,xxx
- hr, 文件r
权限(t_permission)
- id,perm_id
- 文件 r,w,x
- 订单 query,create,xxx
ACL(Access Controll List)
直接用户和权限挂钩
用户(t_user)
- zhangsan
- lisi
用户_权限(t_user_perm)
- zhangsan,文件 r
- zhangsan,文件 x
- zhangsan,订单 query
权限(t_permission)
- id,perm_id
- 文件 r,w,x
- 订单 query,create,xxx
1 |
|
6.2.Spring Security 原理
6.2.1.过滤器链架构
Spring Security利用 FilterChainProxy 封装一系列拦截器链,实现各种安全拦截功能
Servlet三大组件:Servlet、Filter、Listener
6.2.2.FilterChainProxy
6.2.3.SecurityFilterChain
6.3.使用
6.3.1.HttpSecurity
1 |
|
6.3.2.MethodSecurity
1 |
|
核心
- WebSecurityConfigurerAdapter
- @EnableGlobalMethodSecurity: 开启全局方法安全配置
- @Secured
- @PreAuthorize
- @PostAuthorize
- UserDetailService: 去数据库查询用户详细信息的service(用户基本信息、用户角色、用户权限)
6.4.实战
6.4.1.引入依赖
1 |
|
6.4.2.页面
6.4.2.1.首页
1 |
|
6.4.2.2.Hello页
1 |
|
6.4.2.3.登录页
1 |
|
6.4.3.配置类
6.4.3.1.视图控制
1 |
|
6.4.3.2.Security配置
1 |
|
6.4.3.3.改造Hello页
1 |
|
7.可观测性
可观测性 Observability
对线上应用进行观测、监控、预警…
- 健康状况【组件状态、存活状态】Health
- 运行指标【cpu、内存、垃圾回收、吞吐量、响应成功率…】Metrics
- 链路追踪
- …
7.1.SpringBoot Actuator
7.1.1. 实战
7.1.1.1. 场景引入
1 |
|
7.1.1.2.暴露指标
1 |
|
7.1.1.3.访问数据
- 访问 http://localhost:8080/actuator;展示出所有可以用的监控端点
- http://localhost:8080/actuator/beans
- http://localhost:8080/actuator/configprops
- http://localhost:8080/actuator/metrics
- http://localhost:8080/actuator/metrics/jvm.gc.pause
- http://localhost:8080/actuator/endpointName/detailPath
7.1.2. Endpoint
7.1.2.1.常用端点
ID | 描述 |
---|---|
auditevents |
暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件 。 |
beans |
显示应用程序中所有Spring Bean的完整列表。 |
caches |
暴露可用的缓存。 |
conditions |
显示自动配置的所有条件信息,包括匹配或不匹配的原因。 |
configprops |
显示所有@ConfigurationProperties 。 |
env |
暴露Spring的属性ConfigurableEnvironment |
flyway |
显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway 组件。 |
health |
显示应用程序运行状况信息。 |
httptrace |
显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository 组件。 |
info |
显示应用程序信息。 |
integrationgraph |
显示Spring integrationgraph 。需要依赖spring-integration-core 。 |
loggers |
显示和修改应用程序中日志的配置。 |
liquibase |
显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase 组件。 |
metrics |
显示当前应用程序的“指标”信息。 |
mappings |
显示所有@RequestMapping 路径列表。 |
scheduledtasks |
显示应用程序中的计划任务。 |
sessions |
允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。 |
shutdown |
使应用程序正常关闭。默认禁用。 |
startup |
显示由ApplicationStartup 收集的启动步骤数据。需要使用SpringApplication 进行配置BufferingApplicationStartup 。 |
threaddump |
执行线程转储。 |
heapdump |
返回hprof 堆转储文件。 |
jolokia |
通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core 。 |
logfile |
返回日志文件的内容(如果已设置logging.file.name 或logging.file.path 属性)。支持使用HTTPRange 标头来检索部分日志文件的内容。 |
prometheus |
以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus 。 |
threaddump
、heapdump
、metrics
7.1.2.2.定制端点
- 健康监控:返回存活、死亡
- 指标监控:次数、率
1.HealthEndpoint
1 |
|
1 |
|
1 |
|
2.MetricsEndpoint
1 |
|
7.2.监控案例落地
基于:Prometheus + Grafana
7.2.1.安装 Prometheus + Grafana
1 |
|
7.2.2. 导入依赖
1 |
|
1 |
|
访问: http://localhost:8001/actuator/prometheus 验证,返回 prometheus 格式的所有指标
部署Java应用
1 |
|
确认可以访问到: http://8.130.32.70:9999/actuator/prometheus
docker部署Java应用
1、在idea项目目录的target文件夹下新建Dockerfile文件内容如下
1 |
|
2、开启docker远程连接
编辑docker.server文件
1 |
|
找到 [Service] 节点,修改 ExecStart 属性,增加 -H tcp://0.0.0.0:2375
1 |
|
3、idea添加docker连接配置
3、dockerfile配置如下
4、项目文件pom.xml添加如下插件配置
1 |
|
5、在idea中直接执行Dockerfile文件build出镜像并启动即可
6、确认可以访问到: http://192.168.120.130:8888/actuator/prometheus
7.2.3. 配置 Prometheus 拉取数据
1 |
|
7.2.4.配置 Grafana 监控面板
- 添加数据源(Prometheus)
- 添加面板。可去 dashboard 市场找一个自己喜欢的面板,也可以自己开发面板;Dashboards | Grafana Labs
7.2.5. 效果
8.AOT
8.1.AOT与JIT
AOT:Ahead-of-Time(提前编译):程序执行前,全部被编译成机器码
JIT:Just in Time(即时编译): 程序边编译,边运行;
编译:
- 源代码(.c、.cpp、.go、.java。。。) ===编译=== 机器码
语言:
- 编译型语言:编译器
- 解释型语言:解释器
8.1.1.Complier 与 Interpreter
对比项 | 编译器 | 解释器 |
---|---|---|
机器执行速度 | 快,因为源代码只需被转换一次 | 慢,因为每行代码都需要被解释执行 |
开发效率 | 慢,因为需要耗费大量时间编译 | 快,无需花费时间生成目标代码,更快的开发和测试 |
调试 | 难以调试编译器生成的目标代码 | 容易调试源代码,因为解释器一行一行地执行 |
可移植性(跨平台) | 不同平台需要重新编译目标平台代码 | 同一份源码可以跨平台执行,因为每个平台会开发对应的解释器 |
学习难度 | 相对较高,需要了解源代码、编译器以及目标机器的知识 | 相对较低,无需了解机器的细节 |
错误检查 | 编译器可以在编译代码时检查错误 | 解释器只能在执行代码时检查错误 |
运行时增强 | 无 | 可以动态增强 |
8.1.2. AOT 与 JIT 对比
JIT | AOT | |
---|---|---|
优点 | 1.具备实时调整能力 2.生成最优机器指令 3.根据代码运行情况优化内存占用 | 1.速度快,优化了运行时编译时间和内存消耗 2.程序初期就能达最高性能 3.加快程序启动速度 |
缺点 | 1.运行期边编译速度慢 2.初始编译不能达到最高性能 | 1.程序第一次编译占用时间长 2.牺牲高级语言一些特性 |
在 OpenJDK 的官方 Wiki 上,介绍了HotSpot 虚拟机一个相对比较全面的、即时编译器(JIT)中采用的优化技术列表。
可使用:-XX:+PrintCompilation 打印JIT编译信息
8.1.3. JVM架构
.java === .class === 机器码
JVM: 既有解释器,又有编辑器(JIT:即时编译);
8.1.4.Java的执行过程
建议阅读:
1. 流程概要
解释执行:
编译执行:
2. 详细流程
热点代码:调用次数非常多的代码
8.1.5. JVM编译器
JVM中集成了两种编译器,Client Compiler 和 Server Compiler;
- Client Compiler注重启动速度和局部的优化
- Server Compiler更加关注全局优化,性能更好,但由于会进行更多的全局分析,所以启动速度会慢。
Client Compiler:
- HotSpot VM带有一个Client Compiler C1编译器
- 这种编译器启动速度快,但是性能比较Server Compiler来说会差一些。
- 编译后的机器码执行效率没有C2的高
Server Compiler:
- Hotspot虚拟机中使用的Server Compiler有两种:C2 和 Graal。
- 在Hotspot VM中,默认的Server Compiler是C2编译器。
8.1.6. 分层编译
Java 7开始引入了分层编译(Tiered Compiler)的概念,它结合了C1和C2的优势,追求启动速度和峰值性能的一个平衡。分层编译将JVM的执行状态分为了五个层次。五个层级分别是:
- 解释执行。
- 执行不带profiling的C1代码。
- 执行仅带方法调用次数以及循环回边执行次数profiling的C1代码。
- 执行带所有profiling的C1代码。
- 执行C2代码。
profiling就是收集能够反映程序执行状态的数据。其中最基本的统计数据就是方法的调用次数,以及循环回边的执行次数。
- 图中第①条路径,代表编译的一般情况,热点方法从解释执行到被3层的C1编译,最后被4层的C2编译。
- 如果方法比较小(比如Java服务中常见的getter/setter方法),3层的profiling没有收集到有价值的数据,JVM就会断定该方法对于C1代码和C2代码的执行效率相同,就会执行图中第②条路径。在这种情况下,JVM会在3层编译之后,放弃进入C2编译,直接选择用1层的C1编译运行。
- 在C1忙碌的情况下,执行图中第③条路径,在解释执行过程中对程序进行profiling ,根据信息直接由第4层的C2编译。
- 前文提到C1中的执行效率是1层>2层>3层,第3层一般要比第2层慢35%以上,所以在C2忙碌的情况下,执行图中第④条路径。这时方法会被2层的C1编译,然后再被3层的C1编译,以减少方法在3层的执行时间。
- 如果编译器做了一些比较激进的优化,比如分支预测,在实际运行时发现预测出错,这时就会进行反优化,重新进入解释执行,图中第⑤条执行路径代表的就是反优化。
总的来说,C1的编译速度更快,C2的编译质量更高,分层编译的不同编译路径,也就是JVM根据当前服务的运行情况来寻找当前服务的最佳平衡点的一个过程。从JDK 8开始,JVM默认开启分层编译。
云原生:Cloud Native; Java小改版;
最好的效果:
存在的问题:
- java应用如果用jar,解释执行,热点代码才编译成机器码;初始启动速度慢,初始处理请求数量少。
- 大型云平台,要求每一种应用都必须秒级启动。每个应用都要求效率高。
希望的效果:
- java应用也能提前被编译成机器码,随时极速启动,一启动就极速运行,最高性能
- 编译成机器码的好处:
- 另外的服务器还需要安装Java环境
- 编译成机器码的,可以在这个平台 Windows X64 直接运行。
原生镜像:native-image(机器码、本地镜像)
- 把应用打包成能适配本机平台 的可执行文件(机器码、本地镜像)
8.2. GraalVM
GraalVM是一个高性能的JDK,旨在加速用Java和其他JVM语言编写的应用程序的执行,同时还提供JavaScript、Python和许多其他流行语言的运行时。
GraalVM提供了两种运行Java应用程序的方式:
- 在HotSpot JVM上使用Graal即时(JIT)编译器
- 作为预先编译(AOT)的本机可执行文件运行(本地镜像)。
GraalVM的多语言能力使得在单个应用程序中混合多种编程语言成为可能,同时消除了外部语言调用的成本。
8.2.1. 架构
8.2.2. 安装
跨平台提供原生镜像原理:
1. VisualStudio
https://visualstudio.microsoft.com/zh-hans/free-developer-offers/
别选中文
记住你安装的地址;
2. GraalVM
1. 安装
下载 GraalVM + native-image
2. 配置
修改 JAVA_HOME 与 Path,指向新bin路径
验证JDK环境为GraalVM提供的即可:
3. 依赖
安装 native-image 依赖:
1.网络环境好:参考: https://www.graalvm.org/latest/reference-manual/native-image/#install-native-image
1 |
|
2.网络不好,使用我们下载的离线jar;native-image-xxx.jar
文件
1 |
|
4. 验证
1 |
|
3.测试
1. 创建项目
创建普通java项目。编写HelloWorld类;
- 使用
mvn clean package
进行打包 - 确认jar包是否可以执行
java -jar xxx.jar
- 可能需要给
MANIFEST.MF
添加Main-Class: 你的主类
2.编译镜像
- 编译为原生镜像(native-image):使用
native-tools
终端
1 |
|
3. Linux平台测试
1.安装gcc等环境
1 |
|
2.下载安装配置Linux下的GraalVM、native-image
- 下载: https://www.graalvm.org/downloads/
- 安装:GraalVM、native-image
- 配置:JAVA环境变量为GraalVM
1 |
|
3.安装native-image
1 |
|
4.使用native-image编译jar为原生程序
1 |
|
8.3. SpringBoot整合
8.3.1.依赖导入
1 |
|
8.3.2.生成native-image
1、运行aot提前处理命令:mvn springboot:process-aot
2、运行native打包:mvn -Pnative native:build
1 |
|
8.3.3. 常见问题
可能提示如下各种错误,无法构建原生镜像,需要配置环境变量;
- 出现
cl.exe
找不到错误 - 出现乱码
- 提示
no include path set
- 提示fatal error LNK1104: cannot open file ‘LIBCMT.lib’
- 提示 LINK : fatal error LNK1104: cannot open file ‘kernel32.lib’
- 提示各种其他找不到
需要修改三个环境变量:Path
、INCLUDE
、lib
1、 Path:添加如下值
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.33.31629\bin\Hostx64\x64
2、新建
INCLUDE
环境变量:值为
1 |
|
- 3、新建
lib
环境变量:值为
1 |
|