前言
博客须知
-
本文来源于尚硅谷雷神的32.Web开发-【代码式】-WebMvcConfigurer使用哔哩哔哩bilibili以及他的语雀笔记2、SpringBoot3-Web开发 (yuque.com)
-
作者对雷神视频和语雀中的笔记进行了提炼和整理,由于本文的图片使用的是本地路径,所以上传到博客时图片无法正常显示,有图片需求的伙伴可以下载上方的pdf【服了呀,csdn把我有序列表的符号都搞没了。。。。】
-
有一说一,相比于springboot2,雷神的笔记稍微没有那么乱了,自动配置原理那一块讲的很清晰
-
但是
-
视频课程很多细节都没有讲到,比如日志那一块就是带着了解了一下怎么用而已
-
同时语雀的部分笔记以及雷神讲解得有点乱
-
静态资源那一块规则和原理是笔记是分开的,但讲课时又是合在一起讲的,就导致这一块的笔记有点乱
-
笔记中Web开发-》WebMvcAutoConfiguration原理-》为什么容器中放一个WebMvcConfigurer就能配置底层行为,这一块笔记很差劲呀,表达得非常随意【我已经尽可能加上我的理解了,奈何雷神这一块讲得跟坨屎一样,我没法整理清晰】
-
-
-
另外说一下,本篇博客只包含视频中核心特性的内容【大概占视频内容的70%,对应视频p1-》p66】,这一板块在该视频中是最重要的,后续板块等我看完再继续发布
总体感受
-
原理部分除了自动装配那里讲的挺好的,其它原理就有些小乱【尤其是
为什么容器中放一个WebMvcConfigurer就能配置底层行为
】 -
其次,视频大多数知识点只是带着了解和使用了一下,细节方面相对较少
-
还有,笔记结构有点小乱
-
WebMvcAutoConfiguration原理和静态资源是分开的
-
内容协商和内容协商原理又放在了一起【不过内容协商原理讲的还行】
-
这一块为了尊重雷神,我还是参考了他的笔记结构【晕大头,有点小强迫症了】
-
-
雷神能力很强,但是由于他的语言表达能力实在一般,导致部分内容表述得非常抽象【晕大头】
学习建议
-
关于springboot核心,个人感觉是自动装配原理,雷神将很多内容都是围绕自动装配原理展开的,所以这一块需要重点学习【在本文快速入门-》应用分析】
-
原理部分
-
自动装配讲的不错,必学
-
内容协商和错误处理的原理讲的也可以
-
WebMvcAutoConfiguration原理包含的内容有点多,但是鱼龙混杂,讲的不好的部分也不要深究,毕竟雷神的表达能力有限
-
-
本文标识了解的内容,就真的只用了解一下就行了,什么Thymeleaf和国际化过一遍就行了,感觉没有学习的必要
-
如果以后看视频学项目有提到Thymeleaf,直接复制粘贴就行了,不必深究
-
-
本文标识!!!的内容是需要重点学习的,尤其是核心原理中自定义starter,一定要跟着敲一遍,会加深你对自动配置原理的理解,而且还会提高你使用好自动配置的能力
-
如果没有学习过springboot,那就跟着雷神的spring boot3的视频学,没必要去看spring boot2的视频了【很多内容都是和springboot2重合的,而且spring boot3至少需要jdk17的版本,你总有一天要换新的jdk版本】
-
这一个板块花费了我10天时间【一天学习2~4小时,压力不算太大,而且有不少收获】
快速入门
基础入门【了解】
SpringBoot是什么
-
简介
-
SpringBoot 帮我们简单、快速地创建一个独立的、生产级别的 Spring 应用(说明:SpringBoot底层是Spring)
-
大多数 SpringBoot 应用只需要编写少量配置即可快速整合 Spring 平台以及第三方技术
-
-
特性
-
通过导包、写配置、启动运行就能快速创建独立的Spring应用
-
直接嵌入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
-
-
总结:简化开发,简化配置,简化整合,简化部署,简化监控,简化运维
开发流程
-
导入springboot场景启动依赖,所有springboot项目都必须继承自 spring-boot-starter-parent
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.5</version> </parent>
-
导入web场景依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
-
主程序
@SpringBootApplication public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class,args); } }
-
业务测试,springboot默认扫描主程序所在包下的组件
@RestController public class HelloController { @ResponseBody @GetMapping("/hello") public String hello(){ return "hello"; } }
-
导入SpringBoot应用打包插件
-
mvn clean package
把项目打成可执行的jar包 -
java -jar demo.jar
启动项目
-
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
==》
特性小结
-
简化整合
-
导入相关的场景,拥有相关的功能,官方默认支持的所有场景
-
官方提供的场景:命名为:
spring-boot-starter-*
-
第三方提供场景:命名为:
*-spring-boot-starter
-
-
简化开发:无需编写任何配置,直接开发业务
-
简化配置
-
集中式管理配置。只需要修改
application.properties
文件即可 -
配置基本都有默认值
-
-
简化部署
-
springboot项目都可以打包为可执行的jar包
-
只要linux服务器上有java环境,就直接允许jar包即可,无需配置tomcat服务器
-
-
简化运维:修改配置(外部放一个application.properties文件)、监控、健康检查
Spring Initializr 创建向导
-
填写基本信息
.
-
选择需要用到的场景
.
-
一键创建好项目结构
.
!!!应用分析
依赖管理机制
-
为什么导入
starter-web
所有相关依赖都导入进来?-
导入场景启动器就会自动把这个场景的所有核心依赖全部导入
-
maven依赖传递原则:A->B->C,A就拥有B和C
-
-
为什么版本号都不用写?
-
每个boot项目都有一个父项目
spring-boot-starter-parent
-
parent的父项目是
spring-boot-dependencies
-
父项目是版本仲裁中心,把所有常见的jar的依赖版本都声明好了
-
.
-
自定义版本号可以利用maven的就近原则【就是覆盖父项目的依赖版本】
-
直接在当前项目
properties
标签中声明父项目版本属性绑定的key
<properties> <java.version>17</java.version> <mysql.version>8.0.31</mysql.version> </properties>
-
直接在导入依赖的时候声明版本
<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.31</version> </dependency>
-
-
第三方的jar包需要声明版本,即boot父项目没有管理的依赖需要自行声明好版本
<!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.16</version> </dependency>
-
不指定版本会报如下错误
.
-
!!!自动配置机制
初步理解
-
导入场景,容器中就会自动配置好这个场景的核心组件
-
以前需要自己配置和导入DispatcherServlet、ViewResolver、CharacterEncodingFilter....
-
现在导入场景就会自动配置好的这些组件
-
验证:容器中有了什么组件,就具有什么功能
-
public static void main(String[] args) { //java10: 局部变量类型的自动推断 var ioc = SpringApplication.run(MainApplication.class, args); //1、获取容器中所有组件的名字 String[] names = ioc.getBeanDefinitionNames(); //2、挨个遍历: // dispatcherServlet、beanNameViewResolver、characterEncodingFilter、multipartResolver // SpringBoot把以前配置的核心组件现在都给我们自动配置好了。 for (String name : names) { System.out.println(name); } }
-
默认的包扫描规则
-
@SpringBootApplication
标注的类就是主程序类 -
自动的component-scan【包扫描】功能:SpringBoot只会自动扫描主程序所在的包及其下面的子包
-
自定义扫描路径
-
@SpringBootApplication(scanBasePackages = "com.atguigu")
-
@ComponentScan("com.atguigu")
直接指定扫描的路径
-
-
-
配置默认值
-
配置文件的所有配置项是和某个类的对象值进行一一绑定的
-
绑定了配置文件中配置项的类被称为属性类
-
比如
-
ServerProperties
绑定了所有Tomcat服务器有关的配置 -
MultipartProperties
绑定了所有文件上传相关的配置
-
-
配置项的默认值可以参照官方文档或者参照绑定的属性类
-
-
按需加载自动配置
-
场景启动器除了会导入相关功能依赖,还导入一个
spring-boot-starter
【是所有starter
的starter
,基础核心starter】 -
spring-boot-starter
导入了一个包spring-boot-autoconfigure
。包里面都是各种场景的AutoConfiguration
自动配置类 -
虽然全场景的自动配置都在
spring-boot-autoconfigure
这个包,但是不是全都开启的,导入哪个场景就开启哪个自动配置
-
.
-
总结: 导入场景启动器--》触发
spring-boot-autoconfigure
这个包的自动配置生效--》容器中就会具有相关场景的功能
!!!完整流程【面试重点】
1.导入starter-web为例:导入了web开发场景
-
场景启动器导入了相关场景的所有依赖:
starter-json
、starter-tomcat
、springmvc
-
每个场景启动器都引入了一个
spring-boot-starter
【核心场景启动器】.
-
核心场景启动器引入了
spring-boot-autoconfigure
包.
-
spring-boot-autoconfigure
里面囊括了所有场景的所有配置(这些配置类做了整合操作)-
只要这个包下的所有类都能生效,那么相当于SpringBoot官方写好的整合功能就生效了
-
SpringBoot默认只扫描主程序所在的包,所以扫描不到
spring-boot-autoconfigure
下写好的所有配置类 -
这一步只是把
spring-boot-autoconfigure
中的配置类先引入到项目中,此时还不能生效
-
2.主程序:@SpringBootApplication
-
@SpringBootApplication
由三个注解组成@SpringBootConfiguration
、@EnableAutoConfiguratio
、@ComponentScan
-
SpringBoot默认只能扫描自己主程序所在的包及其下面的子包,扫描不到
spring-boot-autoconfigure
包中官方写好的配置类 -
@EnableAutoConfiguration
:SpringBoot 开启自动配置的核心@AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration
-
@Import(AutoConfigurationImportSelector.class)
批量给容器中导入组件 -
SpringBoot启动会默认加载以下的配置类【关注AutoConfigurationImportSelector类中的getCandidateConfigurations方法】
.
-
这些配置类来自于
spring-boot-autoconfigure
下META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件.
-
项目启动的时候利用@Import批量导入组件机制把
autoconfigure
包下的自动配置类导入进来
-
-
虽然批量导入了多个自动配置类,但是这些配置类是按需生效,每一个自动配置类,都有条件注解
@ConditionalOnxxx
,只有条件成立,才能生效
3.xxxxAutoConfiguration自动配置类
-
使用@Bean往容器中放一堆组件
-
每个自动配置类都可能有这个注解
@EnableConfigurationProperties(ServerProperties.class)
,用来把配置文件中配的指定前缀的属性值封装到xxxProperties
属性类中-
以Tomcat为例:服务器的所有配置都是以
server
开头的,将这些配置都封装到了属性类中
-
-
给容器中放的所有组件的一些核心参数,都来自于
xxxProperties
。xxxProperties
都绑定了配置文件,只需要改配置文件的值,核心组件的底层参数都能修改
核心流程总结
-
导入
starter
,就会导入autoconfigure
包 -
autoconfigure
包里面有一个文件META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
,里面指定的所有启动要加载的自动配置类 -
@EnableAutoConfiguration会自动把上面文件中的所有自动配置类都导入进来。xxxAutoConfiguration按照条件注解进行按需加载
-
xxxAutoConfiguration
给容器中导入一堆组件,组件都是从xxxProperties
中提取属性值 -
xxxProperties
和配置文件进行了绑定
-
效果:导入
starter
、修改配置文件,就能修改底层行为
如何学好SpringBoot【了解】
学习要求
-
框架的框架、底层基于Spring。能调整每一个场景的底层行为。100%项目一定会用到底层自定义
-
普通开发:导入starter,Controller、Service、Mapper、偶尔修改配置文件
-
高级开发:自定义组件、自定义配置、自定义starter
-
-
理解自动配置原理:导入starter --> 生效xxxxAutoConfiguration --> 组件 --> xxxProperties --> 配置文件
-
理解其他框架底层,如MyBatis拦截器等
-
可以随时定制化任何组件:配置文件、自定义组件
-
核心
-
这个场景自动配置导入了哪些组件,能不能Autowired进来使用
-
能不能通过修改配置改变组件的一些默认参数
-
需不需要自己完全定义这个组件
-
场景定制化
-
最佳实战
-
选场景,导入到项目
-
官方:starter
-
第三方:去仓库搜
-
-
写配置,改配置文件关键项,例如数据库参数(连接地址、账号密码...)
-
分析这个场景导入了哪些能用的组件
-
自动装配这些组件进行后续使用
-
不满意boot提供的自动配好的默认组件,就定制化(改配置、自定义组件)
-
整合redis为例
-
选场景:
spring-boot-starter-data-redis
,场景AutoConfiguration就是这个场景的自动配置类 -
写配置
-
分析这个场景的自动配置类开启了哪些属性绑定关系==》
@EnableConfigurationProperties(RedisProperties.class)
-
修改redis相关的配置
-
-
分析组件
-
RedisAutoConfiguration
给容器中放了StringRedisTemplate
-
所以可以给业务代码中自动装配
StringRedisTemplate
来进行reids相关操作
-
-
定制化
-
修改配置文件
-
自定义组件,自己给容器中放一个
StringRedisTemplate
-
核心技能
常用注解
-
SpringBoot摒弃XML配置方式,改为全注解驱动
组件注册
注解
-
@Configuration、@SpringBootConfiguration:标识当前类是一个配置类,替代之前的配置文件,配置类本身也是容器中的组件
-
@Bean:替代以前的bean标签,标识在方法上,向容器注入组件,默认以方法名作为容器中的名称
-
@Scope:定制组件的范围,组件默认为单例
@Configuration public class TestConfiguration { @Scope("prototype")//设置为多例 @Bean public User user(){ var user=new User(); return user; } }
-
@Import:可以导入第三方的组件
-
也可以使用@Bean导入,但是无法使用@Component标识在第三方类上【因为那是别人写好的类,怎么可能修改人家源码】
-
注意:@Import导入的组件默认名称为全类名
-
-
@Controller、 @Service、@Repository、@Component:标识在类上交给容器管理
-
@ComponentScan:扫描指定包的组件
步骤
-
@Configuration 编写一个配置类
-
在配置类中,自定义方法给容器中注册组件,配合@Bean使用
-
或使用@Import 导入第三方的组件
条件注解
基础概念
-
条件注解的作用:如果注解指定的条件成立,则触发指定行为
-
常见的条件注解
-
@ConditionalOnClass:如果类路径中存在这个类,则触发指定行为
-
@ConditionalOnMissingClass:如果类路径中不存在这个类,则触发指定行为
-
@ConditionalOnBean:如果容器中存在这个Bean(组件),则触发指定行为
-
@ConditionalOnMissingBean:如果容器中不存在这个Bean(组件),则触发指定行为
-
@ConditionalOnBean(value=组件类型,name=组件名字):判断容器中是否有这个类型的组件,并且名字是指定的值
-
场景
-
如果存在
FastsqlException
这个类,给容器中放一个名为cat01的Cat
组件 -
否则,就给容器中放一个名dog01的
Dog
组件 -
如果系统中有
dog01
这个组件,就给容器中放一个 User组件,名zhangsan -
否则,就放一个User,名叫lisi
@ConditionalOnClass(name = "com.alibaba.druid.FastsqlException") @Bean public Cat cat01(){ return new Cat(); } @ConditionalOnMissingClass("com.alibaba.druid.FastsqlException") @Bean public Dog dog01(){ return new Dog(); } =======测试代码========= var ioc = SpringApplication.run(Boot301DemoApplication.class, args); for (String s : ioc.getBeanNamesForType(Cat.class)) { System.out.println("cat:"+s); } for (String s : ioc.getBeanNamesForType(Dog.class)) { System.out.println("dog:"+s); } =======输出结果========= cat:cat01 user:lisi =======移除druid的依赖后的输出结果========= dog:dog01 user:zhangsan
!!!属性绑定
基础知识
-
@ConfigurationProperties: 将容器中任意组件(Bean)的属性值和配置文件的配置项的值进行绑定
-
给容器中注册组件(@Component、@Bean)
-
使用@ConfigurationProperties声明组件的属性和配置文件进行绑定的前缀
-
-
@EnableConfigurationProperties:开启属性类的属性绑定功能并且将属性类注入到容器中
-
SpringBoot默认只扫描自己主程序所在的包,即使导入的第三方包中的组件上标注了@Component、@ConfigurationProperties注解也无法注入容器
-
因为组件都扫描不进来,此时使用@EnableConfigurationProperties注解就可以快速进行属性绑定并把组件注册进容器
-
-
更多注解参照:Spring注解驱动开发【1-26集】
测试@ConfigurationProperties
@ConfigurationProperties + @Component
@ConfigurationProperties(prefix = "pig") @Component public class Pig { private Long id; private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
配置文件如下
pig.id=12036 pig.name=大猪头
-
测试代码以及测试结果
var ioc = SpringApplication.run(Boot301DemoApplication.class, args); Pig bean = ioc.getBean(Pig.class); System.out.println(bean.getId()+" "+bean.getName()); ======输出======== 12036 ???
-
乱码问题:不仅上述出现乱码,而且配置文件的内容也变为乱码
.
-
乱码解决:在setting中设置文件编码【File Encoding】默认为utf-8
=乱码问题解决=》
@Bean
-
也可以使用@Bean的方式,注入的组件也会自动绑定配置项
@ConfigurationProperties(prefix = "pig") //@Component public class Pig { private Long id; private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } @Bean public Pig pig(){ return new Pig(); } ======测试结果========= 12036 大猪头
-
@ConfigurationProperties()也可以标识在方法上
@ConfigurationProperties(prefix = "pig") @Bean public Pig pig(){ return new Pig(); }
测试@EnableConfigurationProperties
@ConfigurationProperties(prefix = "pig") public class Pig { private Long id; private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
@EnableConfigurationProperties会开启指定的属性类的配置绑定功能,并将其放入容器
@Configuration @EnableConfigurationProperties(Pig.class) public class ConditionConfiguration { } ========输出======= 12036 大猪头
YAML配置文件【了解,之前spring boot2有学习过】
设计目标
痛点:SpringBoot 集中化管理配置,
application.properties
问题:配置多以后难阅读和修改,层级结构辨识度不高
-
设计目标就是方便人类读写
-
层次分明,更适合做配置文件
-
使用
.yaml
或.yml
作为文件后缀
基本语法
-
注意事项
-
大小写敏感
-
使用缩进表示层级关系,k: v,使用空格分割k,v
-
缩进时不允许使用Tab键,只允许使用空格、换行
-
缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
-
#
表示注释,从这个字符一直到行尾都会被解析器忽略
-
-
支持的写法
-
对象:键值对的集合,如:映射(map)/ 哈希(hash) / 字典(dictionary)
-
数组:一组按次序排列的值,如:序列(sequence) / 列表(list)
-
纯量:单个的、不可再分的值,如:字符串、数字、bool、日期
-
示例【这里直接cv】
@Component @ConfigurationProperties(prefix = "person") //和配置文件person前缀的所有配置进行绑定 @Data //自动生成JavaBean属性的getter/setter //@NoArgsConstructor //自动生成无参构造器 //@AllArgsConstructor //自动生成全参构造器 public class Person { private String name; private Integer age; private Date birthDay; private Boolean like; private Child child; //嵌套对象 private List<Dog> dogs; //数组(里面是对象) private Map<String,Cat> cats; //表示Map } @Data public class Dog { private String name; private Integer age; } @Data public class Child { private String name; private Integer age; private Date birthDay; private List<String> text; //数组 } @Data public class Cat { private String name; private Integer age; }
-
properties表示法
person.name=张三 person.age=18 person.birthDay=2010/10/12 12:12:12 person.like=true person.child.name=李四 person.child.age=12 person.child.birthDay=2018/10/12 person.child.text[0]=abc person.child.text[1]=def person.dogs[0].name=小黑 person.dogs[0].age=3 person.dogs[1].name=小白 person.dogs[1].age=2 person.cats.c1.name=小蓝 person.cats.c1.age=3 person.cats.c2.name=小灰 person.cats.c2.age=2
-
yaml表示法
person: name: 张三 age: 18 birthDay: 2010/10/10 12:12:12 like: true child: name: 李四 age: 20 birthDay: 2018/10/10 text: ["abc","def"] #数组/集合的第一种表示方式 dogs: #数组/集合的第二种表示方式 - name: 小黑 age: 3 - name: 小白 age: 2 cats: c1: #key name: 小蓝 age: 3 c2: {name: 小绿,age: 2} #对象也可用{}表示
细节
-
birthDay推荐写为birth-day【yaml中驼峰命名可以转成短横线的写法】
-
文本
-
单引号不会转义【\n 则为普通字符串显示】
-
双引号会转义【\n会显示为换行符】
-
-
大文本
-
|
开头,大文本写在下层,保留文本格式,换行符正确显示
.
-
>
开头,大文本写在下层,换行符都会变成空格显示【如果文本存在缩进就不会压缩】-
压缩换行
文本有缩进
-
-
-
多文档合并:使用
---
可以把多个yaml文档合并在一个文档中,每个文档区依然认为内容独立
.
小技巧:lombok【这玩意确实好用,之前用过,推荐】
简化JavaBean 开发。自动生成构造器、getter/setter、自动生成Builder模式等
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>compile</scope> </dependency>
-
使用
@Data
注解标识在类上就可以自动为属性添加get/set方法
日志配置
简介
-
2024.3.20我的吐槽:关于日志这一块雷神只是简单的过了一下,没有涉及太多细节
规范:项目开发不要编写
System.out.println()
,应该用日志记录信息
.
-
日志门面就相当于日志的接口,springboot默认使用slf4j+logback
-
Spring使用commons-logging作为内部日志,但底层日志实现是开放的。可对接其他日志框架
-
spring5及以后自己整合 commons-logging,不需要导入第三方包
-
-
支持 jul、log4j2、logback。SpringBoot提供了默认的控制台输出配置,也可以配置输出为文件
-
虽然日志框架很多,但是使用SpringBoot 的默认配置就足够用了
-
-
SpringBoot怎么把日志默认配置好的
-
每个
starter
场景,都会导入一个核心场景spring-boot-starter
-
核心场景引入了日志的所有功能
spring-boot-starter-logging
-
-
默认使用了
logback + slf4j
组合作为默认底层日志 -
日志是系统一启动就要使用的组件【启动时机更早】,
xxxAutoConfiguration
是系统启动好了以后才放好的组件 -
日志是利用监听器机制配置好的。
ApplicationListener
-
日志所有的配置都可以通过修改配置文件实现。所有和日志相关的配置都以
logging
为前缀
-
日志格式
2023-03-31T13:56:17.511+08:00 INFO 4944 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2023-03-31T13:56:17.511+08:00 INFO 4944 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.7]
-
默认输出格式
-
时间和日期:毫秒级精度
-
日志级别: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}
-
可修改为
'%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{15} ===> %msg%n'
-
记录日志
Logger logger = LoggerFactory.getLogger(getClass());
-
或者使用Lombok的@Slf4j注解
@Slf4j @RestController public class TestLogging { @RequestMapping("hello") public String helloController(){ log.info("info"); log.debug("debug"); log.warn("warn"); log.error("error"); return "hello"; } }
日志级别
-
由低到高: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>
指定日志级别-
level可取值范围:
TRACE, DEBUG, INFO, WARN, ERROR, FATAL, or OFF
,定义在LogLevel
类中 -
root的logger-name叫root,可以配置logging.level.root=warn,代表所有未指定日志级别都使用 root 的 warn 级别
-
可以指定某个具体的包的日志级别【也可以精确到类】
logging.level.com.study.liao.boot3logging.component=warn
-
-
开发技巧:日志分组
-
SpringBoot 也支持将相关的logger分组在一起,统一配置。比如:Tomcat 相关的日志统一设置
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat logging.level.tomcat=trace
-
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 |
文件输出
-
SpringBoot 默认把日志写在控制台,如果想额外记录到文件,可以在application.properties中添加
logging.file.name
或者是logging.file.path
配置项-
logging.file.name
-
如果只声明名字,日志文件生成到当前项目路径
-
也可以指定路径
logging.file.name=D://Study//FileTest//myLog.log
-
-
logging.file.path
可以生成到指定路径,文件名为spring.log -
如果都指定就只有
logging.file.name
会生效
-
logging.file.name | logging.file.path | 示例 | 效果 |
---|---|---|---|
未指定 | 未指定 | 仅控制台输出 | |
指定 | 未指定 | my.log | 写入指定文件。可以加路径 |
未指定 | 指定 | /var/log | 写入指定目录,文件名为spring.log |
指定 | 指定 | 以logging.file.name为准 |
文件归档与滚动切割
-
归档:每天的日志单独存到一个文档中
-
切割:每个文件10MB,超过大小切割成另外一个文件
-
每天的日志应该独立分割出来存档
-
如果使用logback(SpringBoot 默认整合),可以通过application.properties/yaml文件指定日志滚动规则
logging.logback.rollingpolicy.file-name-pattern=${LONG_FILE}.%d{yyyy-MM-dd}.%i.gz logging.logback.rollingpolicy.max-file-size=100KB
-
如果是其他日志系统,需要自行配置(添加log4j2.xml或log4j2-spring.xml)
-
-
logback支持的滚动规则设置如下
配置项 | 描述 |
---|---|
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) |
自定义配置
命名
日志系统 | 自定义 |
---|---|
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建议在日志配置中使用
-spring
命名(例如,logback-spring.xml
而不是logback.xml
)-
加
-spring
就可以交给spring管理,如果不加就是由logback框架底层在控制
-
-
最佳实战:如果自己要写配置,配置文件名需要加上
xx-spring.xml
切换日志组合
-
导入自己的日志场景,并且排除掉springboot默认的日志场景
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency>
-
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 |
自定义配置【网上cv的log4j2的配置模板】
<?xml version="1.0" encoding="UTF-8"?> <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--> <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数(最小是5秒钟)--> <configuration monitorInterval="5" status="warn"> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--变量配置--> <Properties> <!-- 格式化输出:%date表示日期(可缩写成%d,后同),%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符--> <!-- %logger{36} 表示 Logger 名字最长36个字符 --> <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss,SSS} %highlight{%-5level} [%t] %highlight{%c{1.}.%M(%L)}: %msg%n" /> <!-- 定义日志存储的路径 --> <property name="FILE_PATH" value="log" /> <!--<property name="FILE_NAME" value="myProject" />--> </Properties> <!--此节点有三种常见的子节点:Console,RollingFile,File--> <appenders> <!--target:SYSTEM_OUT或SYSTEM_ERR,一般只设置默认:SYSTEM_OUT--> <console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式,默认为:%m%n,即只输出日志和换行--> <PatternLayout pattern="${LOG_PATTERN}"/> <!--阈值过滤器,控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> </console> <!-- <!–文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用–>--> <!-- <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">--> <!-- <PatternLayout pattern="${LOG_PATTERN}"/>--> <!-- </File>--> <!-- 这个会打印出所有的debug及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileDebug" fileName="${FILE_PATH}/debug.log" filePattern="${FILE_PATH}/debug/DEBUG-%d{yyyy-MM-dd}_%i.log.gz"> <!--阈值过滤器,控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/> <!--如果配置的是“%d{yyyy-MM}”,滚动时间单位就是月。“%d{yyyy-MM-dd}”,滚动时间单位就是天--> <PatternLayout pattern="${LOG_PATTERN}"/> <!--指定滚动日志的策略,就是指定新建日志文件的时机--> <Policies> <!--interval属性用来指定多久滚动一次,时间单位取决于<PatternLayout pattern>,modulate属性调整时间,true:0点为基准滚动,false:服务器启动时间开始滚动--> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <SizeBasedTriggeringPolicy size="100MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖--> <DefaultRolloverStrategy max="15"> <!--删除15天之前的日志--> <Delete basePath="${FILE_PATH}" maxDepth="2"> <IfFileName glob="*/*.log.gz" /> <IfLastModified age="360H" /> </Delete> </DefaultRolloverStrategy> </RollingFile> <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/info/INFO-%d{yyyy-MM-dd}_%i.log.gz"> <!--阈值过滤器,控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,时间单位取决于<PatternLayout pattern>,modulate属性调整时间,true:0点为基准滚动,false:服务器启动时间开始滚动--> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <SizeBasedTriggeringPolicy size="100MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/error/ERROR-%d{yyyy-MM-dd}_%i.log.gz"> <!--阈值过滤器,控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,时间单位取决于<PatternLayout pattern>,modulate属性调整时间,true:0点为基准滚动,false:服务器启动时间开始滚动--> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <SizeBasedTriggeringPolicy size="100MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> <!--启用异步日志,阻塞队列最大容量为20000,超出队列容量时是否等待日志输出,不等待将直接将日志丢弃--> <Async name="Async" bufferSize="20000" blocking="true"> <AppenderRef ref="Console"/> <AppenderRef ref="RollingFileDebug"/> <AppenderRef ref="RollingFileInfo"/> <AppenderRef ref="RollingFileError"/> </Async> </appenders> <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。--> <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效--> <loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息--> <logger name="org.mybatis" level="info" additivity="false"> <AppenderRef ref="Async"/> </logger> <!--监控系统信息--> <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。--> <Logger name="org.springframework" level="info" additivity="false"> <AppenderRef ref="Async"/> </Logger> <!--root 节点用来指定项目的根日志,level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.--> <root level="debug"> <AppenderRef ref="Async" /> </root> </loggers> </configuration>
最佳实战
-
导入任何第三方框架,先排除它的日志包,因为springBoot底层控制好了日志
-
修改
application.properties
配置文件,就可以调整日志的所有行为。如果不够,可以将自己编写日志框架的配置文件放在类路径下就行,比如logback-spring.xml
,log4j2-spring.xml
-
如需对接专业日志系统,也只需要把 logback 记录的日志灌倒 kafka之类的中间件,这和SpringBoot没关系,都是日志框架自己的配置,修改配置文件即可
-
业务中使用slf4j-api记录日志。不要再 sout 了
Web开发
Web场景
自动配置
-
整合web场景
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
-
引入了
autoconfigure
功能 -
@EnableAutoConfiguration
注解使用@Import(AutoConfigurationImportSelector.class)
批量导入组件 -
加载
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中配置的所有组件 -
所有自动配置类如下
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration ====以下是响应式web场景和现在的没关系====== org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.ReactiveMultipartAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration ================以上没关系================= org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
-
绑定了配置文件的一堆配置项
-
SpringMVC的所有配置
spring.mvc
-
Web场景通用配置
spring.web
-
文件上传配置
spring.servlet.multipart
-
服务器的配置
server
,比如编码方式
-
默认效果
-
包含了 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接口
-
最佳实践
springBoot 已经默认配置好了Web开发场景常用功能,直接使用即可
三种方式
方式 | 用法 | 效果 | |
---|---|---|---|
全自动 | 直接编写控制器逻辑 | 全部使用自动配置默认效果 | |
手自一体 | @Configuration + 配置WebMvcConfigurer + 配置 WebMvcRegistrations | 不要标注 @EnableWebMvc | 保留自动配置效果,手动设置部分功能,定义MVC底层组件 |
全手动 | @Configuration + 配置WebMvcConfigurer | 标注 @EnableWebMvc | 禁用自动配置效果,全手动设置 |
-
总结:给容器中写一个配置类
@Configuration
实现WebMvcConfigurer
但是不要标注@EnableWebMvc
注解,实现手自一体的效果
两种模式
-
前后端分离模式:
@RestController
响应JSON数据 -
前后端不分离模式:
@Controller + Thymeleaf
模板引擎
WebMvcAutoConfiguration原理
生效条件
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) //after表示在括号中这些自动配置完成之后执行 @ConditionalOnWebApplication(type = Type.SERVLET) //如果是web应用且类型是SERVLET就生效 @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //容器中没有这个Bean,才生效。默认就是没有 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)//优先级 @ImportRuntimeHints(WebResourcesRuntimeHints.class) public class WebMvcAutoConfiguration { }
-
拓展:web类型包括SERVLET、REACTIVE响应式web
效果
-
放了两个Filter
-
HiddenHttpMethodFilter
:页面表单提交Rest请求(GET、POST、PUT、DELETE) -
FormContentFilter
:由于GET(数据放URL后面)、POST(数据放请求体)请求可以携带数据,PUT、DELETE 的请求体数据会被忽略,因此需要加上该过滤器,防止服务器忽略掉PUT、DELETE 的请求体数据
-
-
给容器中放了
WebMvcConfigurer
组件【详情看下方WebMvcConfigurer接口】:给SpringMVC添加各种定制功能,所有的功能最终会和配置文件进行绑定-
WebMvcProperties绑定
spring.mvc
配置前缀 -
WebProperties绑定
spring.web
配置前缀
-
@Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) //额外导入了其他配置 @EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware{}
WebMvcConfigurer接口
-
提供了配置SpringMVC底层的所有组件入口,add是添加功能,configure是配置功能,extend是扩展功能
.
默认静态资源规则
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/"); addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> { registration.addResourceLocations(this.resourceProperties.getStaticLocations()); if (this.servletContext != null) { ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION); registration.addResourceLocations(resource); } }); }
-
规则一:访问
/webjars/**
路径就去classpath:/META-INF/resources/webjars/
下找资源【不常用】-
使用maven导入依赖,相关的js就导入到
classpath:/META-INF/resources/webjars/
-
然后使用
/webjars/资源名
就可以访问相关的资源
.
-
-
规则二:访问
/**
路径就去静态资源默认的四个位置找资源.
-
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规则
-
如果为true就用最后一次修改的时间来对比服务器和浏览器的资源是否相同,如果相同就返回304【表示资源没有修改】,不同就重新请求资源
-
-
如果浏览器访问了一个静态资源
index.js
,如果服务这个资源没有发生变化,下次访问的时候就可以直接让浏览器用自己缓存中的东西,而不用给服务器发请求
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod())); registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl()); registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
EnableWebMvcConfiguration
//SpringBoot 给容器中放 WebMvcConfigurationSupport 组件 @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(WebProperties.class) public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware { }
-
如果自己放了
WebMvcConfigurationSupport
组件,那SpringBoot的WebMvcAutoConfiguration
都会失效 -
HandlerMapping
: 根据请求路径找到可以处理该请求的handler -
WelcomePageHandlerMapping
-
访问
/**
路径下的所有请求,都在之前提到的四个静态资源路径下找,欢迎页也一样 -
只要静态资源的位置有一个
index.html
页面,项目启动默认访问
-
为什么容器中放一个WebMvcConfigurer就能配置底层行为【这一块讲的云里雾里的】
-
WebMvcAutoConfiguration
是一个自动配置类,它里面有一个EnableWebMvcConfiguration
-
EnableWebMvcConfiguration
继承于DelegatingWebMvcConfiguration
,这两个都生效【??这是什么勾吧表述??】 -
DelegatingWebMvcConfiguration
利用依赖注入(DI)把容器中所有WebMvcConfigurer
注入进来 -
调用
DelegatingWebMvcConfiguration
的方法配置底层规则实际上是调用所有WebMvcConfigurer
的配置底层方法-
来源于语雀simplegq的评论:
DelegatingWebMvcConfiguration
是代理,别人调用它,它就调用容器中所有WebMvcConfigurer的底层配置方法
-
WebMvcConfigurationSupport
-
提供了很多的默认设置,判断系统中是否有相应的类,如果有就加入相应的
HttpMessageConverter
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader); jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
静态资源
前言
-
这一块需要结合前面WebMvcAutoConfiguration原理的内容,雷神将这一部分笔记有点跳,一会讲静态资源一会讲原理,又把笔记分成两个位置,小乱
默认规则
静态资源映射
-
静态资源映射规则在 WebMvcAutoConfiguration 中进行了定义
-
/webjars/**
的所有路径资源都在classpath:/META-INF/resources/webjars/
-
/**
的所有路径资源都在classpath:/META-INF/resources/
、classpath:/resources/
、classpath:/static/
、classpath:/public/
-
所有静态资源都定义了缓存规则。详情请看下方
-
静态资源缓存
-
所有静态资源都定义了缓存规则。【浏览器访问过一次,就会缓存一段时间】,但此功能参数无默认值
-
period: 缓存间隔,默认 0S【说人话就是没有缓存】
-
cacheControl:缓存控制,默认不开启
-
useLastModified:是否使用lastModified头
-
默认 false
-
如果为true就用最后一次修改的时间来对比服务器和浏览器的资源是否相同
-
如果相同就返回304【表示资源没有修改】
-
不同就重新请求资源
-
-
-
欢迎页
-
欢迎页规则在 WebMvcAutoConfiguration 中进行了定义
-
在静态资源目录下找 index.html
-
没有就在 templates下找index模板页
-
缓存实验
-
spring.web为前缀用于配置国际化的区域信息和静态资源策略(开启、处理链、缓存)
#开启静态资源映射规则 spring.web.resources.add-mappings=true #设置缓存 #spring.web.resources.cache.period=3600 ##如果配置更详细的缓存合并项控制,会覆盖period配置:浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求,7200秒以后发请求给服务器 spring.web.resources.cache.cachecontrol.max-age=7200 #使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304 spring.web.resources.cache.use-last-modified=true
自定义静态资源规则
自定义静态资源路径、自定义缓存规则
配置方式
-
spring.mvc
可以配置静态资源访问的前缀路径## 自定义webjars路径前缀 spring.mvc.webjars-path-pattern=/wj/** ## 静态资源访问路径前缀 spring.mvc.static-path-pattern=/static/**
-
例如配置
spring.mvc.static-path-pattern=/static/**
,那访问所以静态资源都得带上/static
的前缀
.
-
-
spring.web
可以配置静态资源目录和静态资源缓存策略【默认的静态资源目录是之前提到的四个目录】
#开启静态资源映射规则【默认开启】 spring.web.resources.add-mappings=true #设置缓存【笼统配置】 spring.web.resources.cache.period=3600 # 精确配置,会覆盖笼统配置 spring.web.resources.cache.cachecontrol.max-age=7200 ## 共享缓存 spring.web.resources.cache.cachecontrol.cache-public=true #使用资源 last-modified 时间,来对比服务器和浏览器的资源是否相同没有变化。相同返回 304 spring.web.resources.cache.use-last-modified=true #自定义静态资源文件夹位置 spring.web.resources.static-locations=classpath:/a/,classpath:/b/,classpath:/static/
代码方式
-
方式一:实现WebMvcConfigurer方法重写addResourceHandlers()方法
-
容器中只要有一个 WebMvcConfigurer 组件,配置的默认底层行为都会生效,不管重写方法有没有调用WebMvcConfigurer.super.addResourceHandlers(registry)都会生效
-
@EnableWebMvc注解会禁用boot的默认配置
-
@Configuration public class MyConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { //WebMvcConfigurer.super.addResourceHandlers(registry);//super就是调用父类的配置,即保留以前的规则 //配置静态资源规则 registry.addResourceHandler("/static/**").//配置访问前缀 addResourceLocations("classpath:/a/","classpath:/b/").//配置静态资源目录 setCacheControl(CacheControl.maxAge(1000, TimeUnit.SECONDS));//配置缓存规则 } }
-
方式二:@Bean注入WebMvcConfigurer组件
@Configuration public class MyConfig { @Bean public WebMvcConfigurer webMvcConfigurer() { return new WebMvcConfigurer() { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").//配置访问前缀 addResourceLocations("classpath:/a/","classpath:/b/").//配置静态资源目录 setCacheControl(CacheControl.maxAge(1000, TimeUnit.SECONDS));//配置缓存规则 } }; } }
路径匹配
Spring5.3 之后加入了更多的请求路径匹配的实现策略
以前只支持 AntPathMatcher 策略, 现在提供了PathPatternParser 策略。并且可以指定具体使用哪种策略
Ant风格路径用法
-
Ant 风格的路径模式语法具有以下规则
-
*:表示任意数量的字符
-
?:表示任意一个字符
-
:表示任意数量的目录
-
{}:表示一个命名的模式占位符
-
[]:表示字符集合,例如[a-z]表示小写字母
-
-
例如
-
*.html
匹配任意名称,扩展名为.html的文件 -
/folder1/*/*.java
匹配在folder1目录下的任意两级目录下的.java文件 -
/folder2/**/*.jsp
匹配在folder2目录下任意目录深度的.jsp文件 -
/{type}/{id}.html
匹配任意文件名为{id}.html,在任意命名的{type}目录下的文件
-
-
注意:Ant 风格的路径模式语法中的特殊字符需要转义
-
要匹配文件路径中的星号,则需要转义为\*
-
要匹配文件路径中的问号,则需要转义为\?
-
模式切换
-
AntPathMatcher 与 PathPatternParser【新版默认使用】
-
PathPatternParser 在 jmh 基准测试下,有 6~8 倍吞吐量提升,降低 30%~40%空间分配率
-
PathPatternParser 兼容 AntPathMatcher语法,并支持更多类型的路径模式
-
PathPatternParser
**
多段匹配的支持仅允许在模式末尾使用
-
@GetMapping("/a*/b?/{p1:[a-f]+}") public String hello(HttpServletRequest request, @PathVariable("p1") String path) { log.info("路径变量p1: {}", path); //获取请求路径 String uri = request.getRequestURI(); return uri; }
-
总结
-
PathPatternParser是默认的路径匹配规则
-
如果路径中间需要用
**
匹配多段路径,就需要替换成ant风格路径
-
# 改变路径匹配策略: # ant_path_matcher 老版策略; # path_pattern_parser 新版策略; spring.mvc.pathmatch.matching-strategy=ant_path_matcher
内容协商
-
实现一套系统适配多端不同类型的数据返回
.
多端内容适配
默认规则
-
基于请求头内容协商(默认开启):客户端向服务端发送请求,携带HTTP标准的Accept请求头
-
Accept:
application/json
、text/xml
、text/yaml
-
服务端根据客户端请求头期望的数据类型进行动态返回
-
-
基于请求参数内容协商(默认禁用)
-
发送请求
GET /projects/spring-boot?format=json
-
匹配到
@GetMapping("/projects/spring-boot")
-
根据参数协商,优先返回json 类型数据【需要开启参数匹配设置】
-
web场景默认导入了jackson【处理json数据相关依赖】,因此默认把对象转成json的格式返回
-
jackson也支持响应xml格式的数据,前提是需要导入相关的依赖【具体实践看下方的效果演示】
-
-
发送请求GET /projects/spring-boot?format=xml,优先返回 xml 类型数据
-
效果演示
-
需求:请求同一个接口,可以返回json和xml不同格式数据
-
导入返回xml格式数据的依赖
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency>
-
标识注解
@JacksonXmlRootElement // 可以响应为xml的数据格式 @Data public class Person { private Long id; private String userName; private String email; private Integer age; }
-
开启基于请求参数的内容协商
# 开启基于请求参数的内容协商功能。 默认参数名:format。 默认此功能不开启 spring.mvc.contentnegotiation.favor-parameter=true # 指定内容协商时使用的参数名。默认是 format spring.mvc.contentnegotiation.parameter-name=type
-
效果
.
配置协商规则与支持类型
-
修改内容协商方式
#使用参数进行内容协商 spring.mvc.contentnegotiation.favor-parameter=true #自定义参数名,默认为format spring.mvc.contentnegotiation.parameter-name=myparam
-
大多数 MediaType 都是开箱即用的。也可以自定义内容类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
内容协商原理HttpMessageConverter
前言
-
HttpMessageConverter
怎么工作?合适工作?-
定制
HttpMessageConverter
来实现多端内容协商 -
编写
WebMvcConfigurer
提供的configureMessageConverters
底层,修改底层的MessageConverter
-
@ResponseBody标识的所有方法的返回值由HttpMessageConverter处理
-
标注了
@ResponseBody
的返回值将会由支持它的HttpMessageConverter
写给浏览器【@ResponseBody
由HttpMessageConverter
处理】 -
如果controller方法的返回值标注了
@ResponseBody
注解-
请求先进入
DispatcherServlet
的doDispatch()
进行处理 -
找到一个
HandlerAdapter
适配器,利用适配器执行目标方法【标识了RequestMapping相关注解就由RequestMappingHandlerAdapter
来执行】-
目标方法执行之前,准备好两个东西
-
HandlerMethodArgumentResolver
:参数解析器,确定目标方法每个参数值 -
HandlerMethodReturnValueHandler
:返回值处理器,确定目标方法的返回值该怎么处理
-
-
最终会调用
invokeHandlerMethod()
来执行目标方法
-
-
RequestMappingHandlerAdapter
里面的invokeAndHandle()
真正执行目标方法 -
目标方法执行完成,会返回返回值对象
-
然后找到一个合适的返回值处理器 【
HandlerMethodReturnValueHandler
】-
遍历所有的返回值处理器
-
最终找到
RequestResponseBodyMethodProcessor
,即可以处理标注了@ResponseBody
注解的方法的处理器
-
-
RequestResponseBodyMethodProcessor
调用writeWithMessageConverters
,利用MessageConverter
把返回值写出去
-
-
HttpMessageConverter
会先进行内容协商-
遍历所有的
MessageConverter
,看谁支持这种内容类型的数据 -
默认
MessageConverter
如下
.
-
因为最终要
json
类型的数据,所以选用支持响应json格式的MappingJackson2HttpMessageConverter
-
jackson用
ObjectMapper
把对象写出去
-
WebMvcAutoConfiguration默认的HttpMessageConverters
-
EnableWebMvcConfiguration
通过addDefaultHttpMessageConverters
添加了默认的MessageConverter
-
ByteArrayHttpMessageConverter
:支持字节数据读写 -
StringHttpMessageConverter
:支持字符串读写 -
ResourceHttpMessageConverter
:支持资源读写 -
ResourceRegionHttpMessageConverter
:支持分区资源写出 -
AllEncompassingFormHttpMessageConverter
:支持表单xml/json读写 -
MappingJackson2HttpMessageConverter
:支持请求响应体Json读写
-
-
系统提供默认的MessageConverter 功能有限,仅用于json或者普通返回数据。额外增加新的内容协商功能,必须增加新的
HttpMessageConverter
自定义内容返回【以yaml为例】
步骤
-
导入支持返回yaml数据的依赖
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> </dependency>
-
测试输出yaml格式的数据:使用YAMLFactory配置yaml的返回格式,并用ObjectMapper的writeValueAsString方法将其转成字符串
public static void main(String[] args) throws JsonProcessingException { Person person = new Person(); person.setId(1L); person.setUserName("张三"); person.setEmail("[email protected]"); person.setAge(18); YAMLFactory factory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER); ObjectMapper mapper = new ObjectMapper(factory); String s = mapper.writeValueAsString(person); System.out.println(s); }
-
编写配置:新增一种内容/媒体类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml spring.mvc.contentnegotiation.media-types.新的格式类型名=对应的媒体类型
-
声明自定义的内容协商类【HttpMessageConverter】
public class MyYamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> { private ObjectMapper objectMapper = null; //把对象转成yaml public MyYamlHttpMessageConverter(){ //给父类构造器加入一个新的媒体类型,即告诉SpringBoot这个MessageConverter支持哪种媒体类型以及字符编码【该媒体类型要和配置文件新增的对应上】 super(new MediaType("text", "yaml", Charset.forName("UTF-8"))); YAMLFactory factory = new YAMLFactory() .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER); this.objectMapper = new ObjectMapper(factory); } @Override protected boolean supports(Class<?> clazz) { //只要是对象类型都支持 return true; } @Override //@RequestBody 指定用什么类型读取接收的对象 protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null; } @Override //@ResponseBody 把对象怎么写出去 protected void writeInternal(Object methodReturnValue, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { //try-with写法,自动关流 try(OutputStream os = outputMessage.getBody()){ this.objectMapper.writeValue(os,methodReturnValue); } } }
-
增加
HttpMessageConverter
组件,专门负责把对象响应为yaml格式
@Bean public WebMvcConfigurer webMvcConfigurer(){ return new WebMvcConfigurer() { @Override //配置一个能把对象转为yaml的messageConverter public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new MyYamlHttpMessageConverter()); } }; }
测试和结论
-
使用Accept请求头
.
-
使用请求参数的内容协商,需要配置以下信息【奇怪,如果用浏览器测试就会下载一个文件】
#使用参数进行内容协商 spring.mvc.contentnegotiation.favor-parameter=true #自定义参数名,默认为format spring.mvc.contentnegotiation.parameter-name=type
-
总结自定义内容返回类型的步骤
-
配置媒体类型支持:
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
-
编写对应的
HttpMessageConverter
,要告诉Boot这个支持的媒体类型【见上面的MyYamlHttpMessageConverter
】 -
容器中放一个
WebMvcConfigurer
组件,并配置底层的MessageConverter
-
模板引擎【了解】
前言
-
由于SpringBoot使用了嵌入式 Servlet容器,所以默认不能使用JSP
-
如果需要服务端页面渲染,优先考虑使用模板引擎
.
-
模板引擎页面默认放在 src/main/resources/templates
-
SpringBoot 包含以下模板引擎的自动配置
-
FreeMarker
-
Groovy
-
Thymeleaf
-
Mustache
-
-
Thymeleaf官网:Thymeleaf
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Good Thymes Virtual Grocery</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel="stylesheet" type="text/css" media="all" th:href="@{/css/gtvg.css}" /> </head> <body> <p th:text="#{home.welcome}">Welcome to our grocery store!</p> </body </html>
Thymeleaf整合
-
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
-
自动配置原理
-
开启了
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
自动配置 -
属性绑定在 ThymeleafProperties 中,对应配置文件 spring.thymeleaf 内容
-
所有的模板页面默认在
classpath:/templates
文件夹下 -
默认效果:会从
classpath:/templates/
目录下找后缀名为.html
的模板页面
-
-
控制器需要标注@Controller注解,会将返回的逻辑视图拼接上模板前后缀组成物理视图
-
即classpath:/templates/逻辑视图.html
-
基础语法
核心用法
th:xxx
-
动态渲染指定的 html 标签属性值、或者th指令(遍历、判断等)
-
th:text
:标签体内文本值渲染 -
th:utext
:不会转义,显示为html原本的样子 -
th:属性
:标签指定属性渲染 -
th:attr
:标签任意属性渲染 -
其他th指令:
th:if
、th:each
...
-
<p th:text="${content}">原内容</p> <a th:href="${url}">登录</a> <img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
表达式:用来动态取值
-
${}
:变量取值,使用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生成工具
语法示例
表达式
-
变量取值:${...}
-
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)
特殊语法
-
无操作:_
所有以上都可以嵌套组合
'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
属性设置
-
th:href="@{/product/list}"
-
th:attr="class=${active}"
-
th:attr="src=@{/images/gtvglogo.png},title=${logo},alt=#{logo}"
-
th:checked="${user.active}"
<p th:text="${content}">原内容</p> <a th:href="${url}">登录</a> <img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
遍历
-
语法:
th:each="元素名,迭代状态 : ${集合}"
-
iterStat 有以下属性
-
index:当前遍历元素的索引,从0开始
-
count:当前遍历元素的索引,从1开始
-
size:需要遍历元素的总数量
-
current:当前正在遍历的元素对象
-
even/odd:是否偶数/奇数行
-
first:是否第一个元素
-
last:是否最后一个元素
-
<tr th:each="prod : ${prods}"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr> <tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr>
判断
-
th:if
<a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:if="${not #lists.isEmpty(prod.comments)}" >view</a>
-
th:switch
<div th:switch="${user.role}"> <p th:case="'admin'">User is an administrator</p> <p th:case="#{roles.manager}">User is a manager</p> <p th:case="*">User is some other thing</p> </div>
属性优先级
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 |
行内写法
-
[[...]] or [(...)]
-
<p>Hello, [[${session.user.name}]]!</p>
变量选择
<div th:object="${session.user}"> <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> </div>
-
等同于
<div> <p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p> </div
模板布局
-
定义片段:
th:fragment
-
引用片段:
~{templatename【模板名】::selector【片段名】}
-
插入片段:
th:insert
、th:replace
<footer th:fragment="copy">© 2011 The Good Thymes Virtual Grocery</footer> <body> <div th:insert="~{footer :: copy}"></div> <div th:replace="~{footer :: copy}"></div> </body> ======= 结果========= <body> <body> <div> <footer>© 2011 The Good Thymes Virtual Grocery</footer> </div> <footer>© 2011 The Good Thymes Virtual Grocery</footer> </body> </body>
devtools
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency>
-
页面修改可以使用
ctrl+F9
可以刷新页面 -
java代码的修改还是使用重启,使用
devtools
热启动可能会引起一些bug,难以排查
国际化【了解】
-
国际化的自动配置参照
MessageSourceAutoConfiguration
-
实现步骤
-
Spring Boot在类路径根下查找messages资源绑定文件,文件名为messages.properties
-
多语言可以定义多个消息文件,命名为
messages_区域代码.properties
-
messages.properties
:默认 -
messages_zh_CN.properties
:中文环境 -
messages_en_US.properties
:英语环境
-
-
在程序中可以自动注入
MessageSource
组件,获取国际化的配置项值@Autowired MessageSource messageSource;//获取国际化消息的组件 @GetMapping("/haha") public String haha(HttpServletRequest request){ //利用代码的方式获取国际化配置文件中指定的配置项的值 Locale locale = request.getLocale();//获取请求中的区域信息 String login = messageSource.getMessage("login", null, locale); return login; }
-
在页面中可以使用表达式
#{}
获取国际化的配置项值
-
-
2024.3.23我的吐槽:这玩意有点鸡肋呀,一套语言就要定制一套配置项
错误处理
默认机制
基础概念
-
错误处理的自动配置都在
ErrorMvcAutoConfiguration
中,有两大核心机制-
SpringBoot会自适应处理错误,根据内容协商响应页面或JSON数据【2024.3.23我的理解,就是可以响应不同类型的错误页面】
-
SpringMVC的错误处理机制依然保留,MVC处理不了才会交给boot进行处理
-
.
SpringMVC错误处理注解
-
@ExceptionHandler表示的方法可以处理错误,默认只能处理本类的指定错误
@ExceptionHandler(Exception.class) public String handleExceptin(Exception e){ return "发生异常的原因"+e.getMessage(); }
-
@ControllerAdvice统一处理所以异常信息
@ControllerAdvice//集中处理所有控制器发生的错误 public class GlobalExceptinHander { @ResponseBody @ExceptionHandler(Exception.class) public String handleExceptin(Exception e){ return "统一处理异常:"+e.getMessage(); } }
原理
流程
-
SpringBoot在底层写好一个BasicErrorController的组件专门处理错误请求【前提是SpringMVC的规则都无法处理】
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE) //返回HTML public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } @RequestMapping //返回 ResponseEntity, JSON public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity<>(status); } Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity<>(body, status); }
-
解析错误请求需要跳转的错误页
//1、解析自定义的错误视图地址 ModelAndView modelAndView = resolveErrorView(request, response, status, model); //2、如果解析不到错误页面的地址,默认的错误页就是 error return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
-
容器中专门有一个错误视图解析器
@Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean(ErrorViewResolver.class) DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resources); }
-
错误视图解析器的默认解析规则
-
发生错误时先获取到错误状态码,获取精确的错误状态码处理页
-
有模板引擎就去templates/error目录下找
-
如果没有模板引擎,在静态资源文件夹下找
-
-
如果没有该状态码的精确页面,那就模糊匹配4xx、5xx的处理页
modelAndView == null && SERIES_VIEWS.containsKey(status.series())
【有无模板引擎的规则同上】.
-
如果模糊匹配都找不到,那就跳转到
/error
【容器中默认有一个名为error的view,提供了默认白页功能】@Bean(name = "error") @ConditionalOnMissingBean(name = "error") public View defaultErrorView() { return this.defaultErrorView; }
@Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { String errorViewName = "error/" + viewName; TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); if (provider != null) { return new ModelAndView(errorViewName, model); } return resolveResource(errorViewName, model); } private ModelAndView resolveResource(String viewName, Map<String, Object> model) { for (String location : this.resources.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location); resource = resource.createRelative(viewName + ".html"); if (resource.exists()) { return new ModelAndView(new HtmlResourceView(resource), model); } } catch (Exception ex) { } } return null; }
-
解析错误页的规则总结
-
如果发生了500、404、503、403 这些错误
-
先精确匹配具体的错误码对应的错误页
-
如果有模板引擎,默认在
classpath:/templates/error/错误状态码.html
-
如果没有模板引擎,在静态资源文件夹下找
错误状态码.html
-
-
如果匹配不到
错误状态码.html
这些精确的错误页,就去找5xx.html
,4xx.html
的模糊匹配-
如果有模板引擎,默认在
classpath:/templates/error/5xx.html
-
如果没有模板引擎,在静态资源文件夹下找
5xx.html
-
-
如果都没有就转发到error视图
-
如果使用模板引擎且
templates
目录下有error.html
页面,就直接渲染 -
否则直接使用默认的白页
-
-
-
拓展:以下组件封装了JSON格式的错误信息
@Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); }
最佳实战
-
前后分离
-
后台发生的所有错误,使用
@ControllerAdvice + @ExceptionHandler
进行统一异常处理
-
-
服务端页面渲染
-
不可预知的服务器或客户端错误,根据错误状态码进行相应处理
-
在
classpath:/templates/error/
下面,放常用精确的错误码页面。500.html
,404.html
-
在
classpath:/templates/error/
下面,放通用模糊匹配的错误码页面。5xx.html
,4xx.html
-
-
发生业务错误
-
核心业务,每一种错误都应该由代码控制,跳转到自己定制的错误页,然后直接
${错误信息字段}
【详情看下方的错误信息】 -
[[${trace}]]
输出错误堆栈信息
-
-
通用业务,定制
classpath:/templates/error.html
页面,显示错误信息
-
-
不论是响应页面还是JSON,可用错误信息如下
.
嵌入式容器
-
Servlet容器:管理、运行Servlet组件(Servlet、Filter、Listener)的环境,一般指服务器
自动配置原理
-
SpringBoot默认嵌入Tomcat作为Servlet容器
-
根据自动配置类分析功能,嵌入式容器的自动配置类是
ServletWebServerFactoryAutoConfiguration
,EmbeddedWebServerFactoryCustomizerAutoConfiguration
@AutoConfiguration @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnClass(ServletRequest.class) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties(ServerProperties.class) @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) public class ServletWebServerFactoryAutoConfiguration { }
-
自动配置原理
-
ServletWebServerFactoryAutoConfiguration
自动配置了嵌入式容器场景 -
绑定了
ServerProperties
配置类,服务器有关的配置绑定了配置文件中以server
为前缀的配置项 -
ServletWebServerFactoryAutoConfiguration
导入了嵌入式的三大服务器Tomcat
、Jetty
、Undertow
-
导入的
Tomcat
、Jetty
、Undertow
都有条件注解,系统中有这个类才会生效(也就是导了包) -
springboot给容器中放了
TomcatServletWebServerFactory
,默认Tomcat
配置生效 -
三大服务器都给容器中放了一个
xxxxServletWebServerFactory
【xxxweb服务器工厂(造web服务器的)】-
web服务器工厂都有一个功能:
getWebServer()
获取web服务器 -
TomcatServletWebServerFactory创建了tomcat
-
-
-
-
ServletWebServerFactory创建webServer的时机
-
ServletWebServerApplicationContext
【ioc容器】启动的时候会调用创建web服务器 -
Spring容器刷新【
onRefresh()
】时候,会预留一个时机,刷新子容器【容器刷新就是容器启动的时候】 -
refresh() 容器刷新十二大步【这一块得去看Spring相关知识】的刷新子容器会调用
onRefresh()
@Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } }
-
-
总结
-
Web场景的Spring容器启动,在onRefresh的时候,会调用创建web服务器的方法
-
Web服务器的创建是通过WebServerFactory搞定的。容器中又会根据条件注解,导了什么包就启动相关的服务器配置
-
默认情况下,
EmbeddedTomcat
会给容器中放一个TomcatServletWebServerFactory
,项目启动就自动创建出Tomcat
-
最佳实战
-
修改
server
为前缀的相关配置就可以修改服务器参数 -
通过给容器中放一个
ServletWebServerFactory
,来禁用掉SpringBoot默认放的服务器工厂,实现自定义嵌入任意服务器
<properties> <servlet-api.version>3.1.0</servlet-api.version> </properties> <!-- 切换服务器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <!-- Use Jetty instead --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency>
全面接管SpringMVC【知识串联】
基础知识
-
SpringBoot默认配置好了 SpringMVC 的所有常用特性
-
如果需要全面接管SpringMVC的所有配置并禁用默认配置,需要编写一个
WebMvcConfigurer
配置类,并标注@EnableWebMvc
即可 -
全手动模式
-
@EnableWebMvc
: 禁用默认配置 -
WebMvcConfigurer
组件:定义MVC的底层行为
-
WebMvcAutoConfiguration自动配置了哪些规则【了解】
-
SpringMVC自动配置场景配置了如下的所有默认行为
.
-
支持RESTful的filter:HiddenHttpMethodFilter
-
支持非POST请求,请求体携带数据:FormContentFilter
-
WebMvcAutoConfigurationAdapter
是一个WebMvcConfigurer
【WebMvcConfigurer
定义了MVC默认的底层行为】-
视图解析器
-
InternalResourceViewResolver
,根据返回的逻辑视图跳转到对应页面 -
BeanNameViewResolver
,根据逻辑视图找到对应的组件【用视图名去匹配组件名】
-
-
内容协商解析器:
ContentNegotiatingViewResolver
-
请求上下文过滤器:
RequestContextFilter
,任意位置都可以直接获取当前请求【这个好像谷粒商城有用到过】 -
静态资源链规则
-
ProblemDetailsExceptionHandler
:捕获SpringMVC内部场景异常的错误详情
-
-
WebMvcAutoConfigurationAdapter
导入了EnableWebMvcConfiguration
,它配置了如下的组件-
RequestMappingHandlerAdapter
-
WelcomePageHandlerMapping
: 欢迎页功能支持(模板引擎/静态资源目录放index.html),项目访问/就展示这个页面 -
RequestMappingHandlerMapping
:找每个请求由谁处理的映射关系 -
ExceptionHandlerExceptionResolver
:默认的异常解析器 -
FormattingConversionService
: 数据格式化 、类型转化 -
Validator
: 数据校验JSR303
提供的数据校验功能 -
WebBindingInitializer
:请求参数的封装与绑定 -
ContentNegotiationManager
:内容协商管理器 -
LocaleResolver
:国际化解析器 -
ThemeResolver
:主题解析器 -
FlashMapManager
:临时数据共享
-
WebMvcConfigurer功能【了解】
-
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 | 无 | 消息码解析器:国际化使用 | 无 |
@EnableWebMvc禁用默认行为
-
@EnableWebMvc
给容器中导入DelegatingWebMvcConfiguration
组件,该组件继承于WebMvcConfigurationSupport
-
WebMvcAutoConfiguration
有一个核心的条件注解,@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
,容器中没有WebMvcConfigurationSupport
,WebMvcAutoConfiguration
才生效 -
@EnableWebMvc 导入
WebMvcConfigurationSupport
导致WebMvcAutoConfiguration
失效。导致禁用了默认行为
-
总结
-
@EnableWebMVC 禁用了Mvc的自动配置
-
WebMvcConfigurer定义SpringMVC底层组件的功能类
-
Web新特性【了解】
Problemdetails
基础知识
-
ProblemDetailsExceptionHandler
是一个@ControllerAdvice
,用于集中处理系统异常
@Configuration(proxyBeanMethods = false) //配置过一个属性 spring.mvc.problemdetails.enabled=true @ConditionalOnProperty(prefix = "spring.mvc.problemdetails", name = "enabled", havingValue = "true") static class ProblemDetailsErrorHandlingConfiguration { @Bean @ConditionalOnMissingBean(ResponseEntityExceptionHandler.class) ProblemDetailsExceptionHandler problemDetailsExceptionHandler() { return new ProblemDetailsExceptionHandler(); } }
-
可以处理以下异常,如果系统出现以下异常,会被SpringBoot支持以
RFC 7807
规范方式返回错误数据,不是默认开启的
@ExceptionHandler({ HttpRequestMethodNotSupportedException.class, //请求方式不支持 HttpMediaTypeNotSupportedException.class, HttpMediaTypeNotAcceptableException.class, MissingPathVariableException.class, MissingServletRequestParameterException.class, MissingServletRequestPartException.class, ServletRequestBindingException.class, MethodArgumentNotValidException.class, NoHandlerFoundException.class, AsyncRequestTimeoutException.class, ErrorResponseException.class, ConversionNotSupportedException.class, TypeMismatchException.class, HttpMessageNotReadableException.class, HttpMessageNotWritableException.class, BindException.class })
使用
-
没有开启ProblemDetails功能,状态码为405的json类型的错误信息响应格式如下
{ "timestamp": "2023-04-18T11:13:05.515+00:00", "status": 405, "error": "Method Not Allowed", "trace": "org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' is not supported\r\n\tat ... , "message": "Method 'POST' is not supported.", "path": "/list" }
-
开启ProblemDetails功能,使用新的MediaType
Content-Type: application/problem+json
+,还可以扩展额外的错误信息
{ "type": "about:blank", "title": "Method Not Allowed", "status": 405, "detail": "Method 'POST' is not supported.", "instance": "/list" }
函数式Web【可以跳过】
基础知识
-
SpringMVC 5.2
以后允许使用函数式的方式,定义Web的请求处理流程【函数式接口】 -
Web请求处理的方式
-
@Controller + @RequestMapping
:耦合式 (路由、业务耦合) -
函数式Web:分离式(路由、业务分离)
-
-
核心类
-
RouterFunction:定义路由信息
-
RequestPredicate:定义请求规则,即请求方式、接收的请求参数等
-
ServerRequest:封装请求数据
-
ServerResponse:封装响应数据
-
-
场景:User RESTful - CRUD
-
GET /user/1 获取1号用户
-
GET /users 获取所有用户
-
POST /user 请求体携带JSON,新增一个用户
-
PUT /user/1 请求体携带JSON,修改1号用户
-
DELETE /user/1 删除1号用户
-
示例
-
使用步骤
-
给容器中放一个RouterFunction<ServerResponse>类型的组件【集中定义路由信息】
-
router()
方法用于定义路由信息 -
使用对应的业务处理器处理请求
-
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.web.servlet.function.RequestPredicate; import org.springframework.web.servlet.function.RouterFunction; import org.springframework.web.servlet.function.ServerResponse; import static org.springframework.web.servlet.function.RequestPredicates.accept; import static org.springframework.web.servlet.function.RouterFunctions.route; @Configuration(proxyBeanMethods = false) public class MyRoutingConfiguration { private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON); @Bean public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler/*自动注入业务处理器*/) { return route() //请求路径 接收参数类型 交给谁处理,用什么方法处理【每个业务都准备自己的handler】 .GET("/{user}", ACCEPT_JSON, userHandler::getUser) .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers) .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser) .build();//构建出上述规则的RouterFunction } }
-
业务处理器如下
import org.springframework.stereotype.Component; import org.springframework.web.servlet.function.ServerRequest; import org.springframework.web.servlet.function.ServerResponse; @Component public class MyUserHandler { public ServerResponse getUser(ServerRequest request) { ... return ServerResponse.ok().build(); } public ServerResponse getUserCustomers(ServerRequest request) { ... return ServerResponse.ok().build(); } public ServerResponse deleteUser(ServerRequest request) { ... return ServerResponse.ok().build(); } }
数据访问--整合SSM场景
整合步骤
-
创建项目
,springboot初始化向导自动导入了以下场景
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>
-
配置数据库连接信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/study spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.username=root spring.datasource.password=1212go12
-
配置MyBatis相关信息
-
小技巧:安装MyBatisX插件,可以帮忙生成Mapper接口的xml文件
-
#指定mapper映射文件位置 mybatis.mapper-locations=classpath:/mapper/*.xml #开启驼峰命名规则,会被数据库中的带有_的字段转成驼峰命名 mybatis.configuration.map-underscore-to-camel-case=true
-
在主启动类中,标识@MapperScan注解,扫描mapper接口所在包,之后就可以自动注入mapper接口对象了
@MapperScan("com.study.liao.boot3ssmtest.mapper") @SpringBootApplication public class Boot3SsmTestApplication { public static void main(String[] args) { SpringApplication.run(Boot3SsmTestApplication.class, args); } }
-
测试
@RestController public class UserController { @Autowired UserMapper userMapper; @RequestMapping("/user/{id}") public TUser getUser(@PathVariable("id") Long id){ return userMapper.getUserName(id); } }
.
自动配置
jdbc场景的自动配置
-
mybatis-spring-boot-starter
导入spring-boot-starter-jdbc
【jdbc是操作数据库的场景】 -
Jdbc
场景的几个自动配置-
数据源的自动配置:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
-
所有和数据源有关的配置都绑定在
DataSourceProperties
-
默认使用
HikariDataSource
-
-
给容器中放了
JdbcTemplate
用于操作数据库【其实这玩意没卵用】:org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration -
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration
-
基于XA二阶提交协议的分布式事务数据源:org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration
-
事务管理器:org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
-
-
jdbc场景具有的底层能力:数据源、JdbcTemplate、事务
MyBatis场景的自动配置
-
mybatis-spring-boot-starter
导入mybatis-spring-boot-autoconfigure
(mybatis的自动配置包)-
默认加载两个自动配置类
-
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration
-
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
.
-
必须在数据源配置好之后才生效
-
MyBatis的所有配置绑定在
MybatisProperties
-
-
-
MybatisAutoConfiguration
的作用-
给容器中放入
SqlSessionFactory
组件,用于创建和数据库的一次会话 -
给容器中放入
SqlSessionTemplate
组件,用于操作数据库
-
-
@MapperScan注解创建每个Mapper接口的代理对象并放到容器中
-
利用
@Import(MapperScannerRegistrar.class)
批量给容器中注册组件 -
解析指定的包路径里面的每一个类,为每一个Mapper接口类,创建Bean定义信息,注册到容器中
-
-
-
总结:
MyBatisAutoConfiguration
配置了MyBatis的整合流程
分析哪些自动配置类生效
-
找
classpath:/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中配置的所有值,就是要开启的自动配置类,但是每个类可能有条件注解,基于条件注解判断哪个自动配置类生效了 -
配置
debug=true
开启调试模式,就可以详细打印出开启的自动配置【快速定位生效的配置】-
Positive是生效的自动配置
-
Negative是未生效的自动配置
-
扩展:整合其他数据源
-
以Druid 数据源为例
-
导入
druid-starter
-
写配置
-
分析自动配置了哪些东西,怎么用
-
#数据源基本配置 spring.datasource.url=jdbc:mysql://192.168.200.100:3306/demo spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.username=root spring.datasource.password=123456 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource # 配置StatFilter监控 spring.datasource.druid.filter.stat.enabled=true spring.datasource.druid.filter.stat.db-type=mysql spring.datasource.druid.filter.stat.log-slow-sql=true spring.datasource.druid.filter.stat.slow-sql-millis=2000 # 配置WallFilter防火墙 spring.datasource.druid.filter.wall.enabled=true spring.datasource.druid.filter.wall.db-type=mysql spring.datasource.druid.filter.wall.config.delete-allow=false spring.datasource.druid.filter.wall.config.drop-table-allow=false # 配置监控页,内置监控页面的首页是 /druid/index.html spring.datasource.druid.stat-view-servlet.enabled=true spring.datasource.druid.stat-view-servlet.login-username=admin spring.datasource.druid.stat-view-servlet.login-password=admin spring.datasource.druid.stat-view-servlet.allow=* # 其他 Filter 配置不再演示 # 目前为以下 Filter 提供了配置支持,请参考文档或者根据IDE提示(spring.datasource.druid.filter.*)进行配置。 # StatFilter # WallFilter # ConfigFilter # EncodingConvertFilter # Slf4jLogFilter # Log4jFilter # Log4j2Filter # CommonsLogFilter
基础特性
SpringApplication【了解】
自定义banner
-
banner就这玩意
.
-
类路径添加banner.txt或设置spring.banner.location的存放路径就可以定制banner
-
推荐网站:Spring Boot banner 在线生成工具,制作下载英文 banner.txt,修改替换 banner.txt 文字实现自定义,个性化启动 banner-bootschool.net
.
-
配置
spring.main.banner-mode=off
可以关闭banner
自定义SpringApplication
-
个人感觉不是很重要,都有配置文件了,用代码修改底层配置有点本末倒置了,了解一下就行
-
拆分写法
-
将运行SpringApplication方法拆分成两步,中间可以修改底层配置
-
配置文件的优先级高于程序化调整的优先级
-
@SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication application = new SpringApplication(MyApplication.class); //1.自定义spring应用的底层设置【但是优先级比配置文件低】 application.setBannerMode(Banner.Mode.OFF);//关闭banner功能 //2.运行spring应用 application.run(args); } }
-
FluentBuilder API(流式写法),注意:必须要用sources(应用名.class)指定源程序,不指定会抛异常
public static void main(String[] args) { new SpringApplicationBuilder() .main(Boot3FeaturesApplication.class) .sources(Boot3FeaturesApplication.class) .bannerMode(Banner.Mode.OFF) .run(args); }
Profiles
基础知识
-
作用:具有环境隔离能力,快速切换开发、测试、生产环境
-
步骤
-
标识环境:指定哪些组件、配置在哪个环境生效
-
切换环境:这个环境对应的所有组件和配置就应该生效
-
使用
指定环境
-
Spring Profiles 提供一种隔离配置的方式,使其仅在特定环境生效
-
容器中的组件(包含配置类)都可以被
@Profile
标记,来指定在什么环境下被加载
@Profile({"text","dev"}) @Component public class Cat { }
-
内部嵌套的组件需要激活内外部环境才能生效
spring.profiles.active=test,dev
,其实很好理解-
如果只有test环境只保证Dog生效,并不保证dev环境下的pig生效
-
只有dev环境,配置类Dog都不生效,怎么将该配置中的组件Pig注入容器
-
@Profile({"test"}) @Configuration public class Dog { @Profile({"dev"}) @Bean public Pig pig(){ return new Pig(); } }
环境激活
-
配置文件激活指定环境【推荐使用】
spring.profiles.active=production,hsqldb
-
也可以使用命令行激活
--spring.profiles.active=dev,hsqldb
.
-
还可以配置默认环境【默认环境是始终生效的】
-
springboot默认环境为default
-
配置默认环境名称
spring.profiles.default=test
-
-
注意:不标注
@Profile
的组件永远生效
环境包含
-
也可以额外添加生效文件,而不是激活替换【不管激活了哪个环境,都额外包含include配置的环境,即总是生效的环境】
spring.profiles.include[0]=common spring.profiles.include[1]=local
-
最佳实战
-
生效的环境 = 激活的环境/默认环境 + 包含的环境
-
项目里面这么用
-
基础的配置【
mybatis
、log
】写到包含环境中 -
需要动态切换的配置【
db
、redis
】写到激活的环境中
-
-
Profile分组
-
创建prod组,指定包含db和mq配置
#写法一 spring.profiles.group.prod[0]=db spring.profiles.group.prod[1]=mq #写法二 spring.profiles.group.prod=db,mq
-
指定
--spring.profiles.active=prod
,就会激活prod,db,mq有关配置
Profile配置文件
-
application.properties
是主配置文件,任何情况下都生效 -
application-{环境名}.properties
可以作为指定环境的配置文件 -
profile优先级>application优先级 ,如果application和激活的环境配置文件都有同一项配置,最终生效的是环境配置文件的配置项
-
激活当前环境对应的配置就会生效,最终生效的所有配置包括
-
application-{当前环境}.properties
:当前环境的配置文件的所有配置 -
application.properties
:主配置文件中和激活文件中不冲突的所有配置项【优先级的原因】
-
-
激活有关的配置项如
spring.profiles.active
和spring.profiles.default
只能写在主配置类中,在指定环境中配置是无效的
19:07:23.658 [main] ERROR org.springframework.boot.SpringApplication -- Application run failed org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property 'spring.profiles.active' imported from location 'class path resource [application-dev.properties]' is invalid in a profile specific resource [origin: class path resource [application-dev.properties] - 3:24] at org.springframework.boot.context.config.InvalidConfigDataPropertyException.lambda$throwIfPropertyFound$1(InvalidConfigDataPropertyException.java:121)
外部化配置
场景引入
-
线上应用如何快速修改配置,并应用最新配置?
-
SpringBoot 使用 配置优先级 + 外部配置 简化配置更新、简化运维
-
只需要给
jar
应用所在的文件夹放一个application.properties
最新配置文件,重启项目就能自动应用最新配置
-
配置优先级
基础概念
-
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)
-
命令行参数
-
如下图所示,也可以使用控制台命令
java -jar xxx.jar --server.port=9999
.
-
-
测试属性(@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配置
-
-
演示场景
-
包内: application.properties
server.port=8000
-
包内: application-dev.properties
server.port=9000
-
包外: application.properties
server.port=8001
-
包外: application-dev.properties
server.port=9001
-
所有参数均可由命令行传入
-
使用
--参数项=参数值
,将会被添加到环境变量中,并优先于配置文件 -
比如
java -jar app.jar --name="Spring"
,可以使用@Value("${name}")获取
-
-
启动端口:命令行 >
9001
>8001
>9000
>8000
-
外部配置
顺序
-
SpringBoot 应用启动时会自动寻找application.properties和application.yaml位置进行加载,顺序如下【由低到高】
-
类路径:内部
-
类根路径
-
类下/config包
-
-
当前路径(项目所在的位置):外部
-
当前路径
-
当前下/config子目录
-
/config目录的直接子目录
-
-
结论
-
命令行 > 包外config直接子目录 > 包外config目录 > 包外根目录 > 包内目录
-
命令行 > 所有
-
包外 > 包内
-
config目录 > 根目录
-
profile > application
-
-
同级比较
-
profile配置 > 默认配置
-
properties配置 > yaml配置
-
-
配置不同就都生效(互补),配置相同高优先级覆盖低优先级
!!!一图流总结【很详细】
.
导入配置
-
使用spring.config.import可以导入额外配置,类似于
@PropertySource
spring.config.import=my.properties my.property=value
-
无论以上写法的先后顺序,my.properties的值总是优先于直接在文件中编写的my.property【导入文件的优先级低于配置文件的优先级,因为外部大于内部,而且该方式的优先级和
@PropertySource
一样】
属性占位符
-
可以使用
${name:default}
形式取出之前配置过的值【:default是为了指定默认值】
@Value("${name:666}") private String name;
-
配置文件中也可以使用
app.name=MyApp app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
单元测试-JUnit5【了解】
整合
-
SpringBoot 提供一系列测试工具集及注解方便测试
-
spring-boot-test提供核心测试能力,spring-boot-test-autoconfigure 提供测试的一些自动配置
-
只需要导入spring-boot-starter-test 即可整合测试,测试类必须在主启动类所在包或者子包,且需要标识
@SpringBootTest
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
-
spring-boot-starter-test 默认提供了JUnit 5、Spring Test、AssertJ、Hamcrest、Mockito、JSONassert、JsonPath库供测试使用
测试
-
组件测试:直接
@Autowired
容器中的组件进行测试 -
注解
-
@Test:标识的方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
-
@ParameterizedTest:表示方法是参数化测试,下方会有详细介绍
-
@RepeatedTest:表示方法可重复执行,下方会有详细介绍
-
@DisplayName:为测试类或者测试方法设置展示名称
-
@BeforeEach:表示在每个单元测试之前执行
-
@AfterEach:表示在每个单元测试之后执行
-
@BeforeAll:表示在所有单元测试之前执行
-
@AfterAll:表示在所有单元测试之后执行
-
@Tag:表示单元测试类别,类似于JUnit4中的@Categories
-
@Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
-
@Timeout:表示测试方法运行如果超过了指定时间将会返回错误
-
@ExtendWith:为测试类或测试方法提供扩展类引用
-
断言
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
assertArrayEquals | 数组断言 |
assertAll | 组合断言 |
assertThrows | 异常断言 |
assertTimeout | 超时断言 |
fail | 快速失败 |
嵌套测试
-
JUnit 5 可以通过 Java 中的内部类和
@Nested
注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起 -
在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制
@DisplayName("A stack") class TestingAStackDemo { Stack<Object> stack; @Test @DisplayName("is instantiated with new Stack()") void isInstantiatedWithNew() { new Stack<>(); } @Nested @DisplayName("when new") class WhenNew { @BeforeEach void createNewStack() { stack = new Stack<>(); } @Test @DisplayName("is empty") void isEmpty() { assertTrue(stack.isEmpty()); } @Test @DisplayName("throws EmptyStackException when popped") void throwsExceptionWhenPopped() { assertThrows(EmptyStackException.class, stack::pop); } @Test @DisplayName("throws EmptyStackException when peeked") void throwsExceptionWhenPeeked() { assertThrows(EmptyStackException.class, stack::peek); } @Nested @DisplayName("after pushing an element") class AfterPushing { String anElement = "an element"; @BeforeEach void pushAnElement() { stack.push(anElement); } @Test @DisplayName("it is no longer empty") void isNotEmpty() { assertFalse(stack.isEmpty()); } @Test @DisplayName("returns the element when popped and is empty") void returnElementWhenPopped() { assertEquals(anElement, stack.pop()); assertTrue(stack.isEmpty()); } @Test @DisplayName("returns the element when peeked but remains not empty") void returnElementWhenPeeked() { assertEquals(anElement, stack.peek()); assertFalse(stack.isEmpty()); } } } }
参数化测试
-
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为单元测试带来许多便利
-
利用@ValueSource等注解,将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码
-
@ValueSource:为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
-
@NullSource:表示为参数化测试提供一个null的入参
-
@EnumSource:表示为参数化测试提供一个枚举入参
-
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
-
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)参数化测试
-
!!!核心原理
事件和监听器
生命周期监听
-
场景:监听应用的生命周期,要在应用启动的某一刻执行某些操作
监听器
-
自定义
SpringApplicationRunListener
来监听事件-
编写
SpringApplicationRunListener
实现类public class MySpringBootListener implements SpringApplicationRunListener { @Override public void starting(ConfigurableBootstrapContext bootstrapContext) { System.out.println("正在启动"); } @Override public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) { System.out.println("环境准备完成"); } @Override public void contextPrepared(ConfigurableApplicationContext context) { System.out.println("上下文【ioc容器】准备完成"); } @Override public void contextLoaded(ConfigurableApplicationContext context) { System.out.println("上下文【ioc容器】加载完成"); } @Override public void started(ConfigurableApplicationContext context, Duration timeTaken) { System.out.println("启动完成"); } @Override public void ready(ConfigurableApplicationContext context, Duration timeTaken) { System.out.println("应用准备就绪"); } @Override public void failed(ConfigurableApplicationContext context, Throwable exception) { System.out.println("启动失败才生效"); } }
-
在
META-INF/spring.factories
中配置接口的全类名=实现类的全类名-
org.springframework.boot.SpringApplicationRunListener=自己的Listener
-
还可以指定一个有参构造器,接受两个参数
(SpringApplication application, String[] args)
org.springframework.boot.SpringApplicationRunListener=com.study.liao.boot3features.listener.MySpringBootListener
.
-
-
测试
.
-
-
springboot在
spring-boot.jar
中默认配置的Listener.
生命周期全流程
-
在项目启动之前,先去
META-INF/spring.factories
读取Listener.
-
引导:利用BootstrapContext引导整个项目启动
-
starting:应用开始,SpringApplication的run方法一调用,只要有了BootstrapContext就执行
-
environmentPrepared:环境准备好(把启动参数等绑定到环境变量中),但是ioc还没有创建【调一次】
-
-
启动
-
contextPrepared:ioc容器创建并准备好,并关闭引导上下文,但是sources(主配置类)没加载,组件都没创建【调一次】
-
contextLoaded:ioc容器加载,主配置类加载进去了,但是ioc容器还没刷新(即bean还没创建)【调一次】
=======截止到这里,ioc容器里面还没创建bean=======
-
started:ioc容器刷新了(所有bean造好了),但是runner没调用
-
ready:ioc容器刷新了(所有bean造好了),所有runner调用完了
-
-
运行:以上步骤都正确执行,代表容器running
.
事件触发时机
各种回调监听器
-
BootstrapRegistryInitializer
:感知引导初始化【感知特定阶段】-
可以在
META-INF/spring.factories
配置,也可以调用application.addBootstrapRegistryInitializer()
-
创建引导上下文
bootstrapContext
的时候触发 -
场景:进行密钥校对授权
-
-
ApplicationContextInitializer
: 感知ioc容器初始化【感知特定阶段】-
可以在
META-INF/spring.factories
配置,也可以调用application.addInitializers()
-
-
ApplicationListener
:基于事件机制感知事件,具体的阶段可以做别的事【感知全阶段的事件】-
可以在
META-INF/spring.factories
配置,也可以调用SpringApplication.addListeners(…)
或SpringApplicationBuilder.listeners(…)
-
也可以通过
@Bean
或@EventListener
注入IOC容器 -
不仅能感知生命周期的事件,还可以感知运行时发生的事件,可以实现基于事件驱动模式的应用开发
-
-
SpringApplicationRunListener
:各种阶段都能自定义操作,功能比ApplicationListener
更完善【感知全阶段生命周期】-
需要在
META-INF/spring.factories
配置
-
-
ApplicationRunner
:感知应用就绪Ready,应用卡死就不会就绪【感知特定阶段】-
只要是容器中的组件就可以被获取到,可以使用【不限于】
@Bean
注入容器
@Bean public ApplicationRunner applicationRunner(){ return args -> { System.out.println("ApplicationRunner运行了,参数为"+args); }; }
-
-
CommandLineRunner
:感知应用就绪Ready,应用卡死就不会就绪【感知特定阶段】-
注入方式和
ApplicationRunner
一模一样
-
自定义ApplicationListener
-
实现接口
public class MyListener implements ApplicationListener<ApplicationEvent> { @Override public void onApplicationEvent(ApplicationEvent event) { System.out.println("事件到达"+event); } }
-
配置信息
org.springframework.context.ApplicationListener=com.study.liao.boot3features.listener.MyListener
-
测试【生命周期每一个阶段都对应一个事件】
事件完整触发流程【九大事件】
-
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
:启动出错
-
下图放在上方的事件是在该阶段之前触发的,下方就是之后触发
.
-
应用事件发送顺序如下
-
感知应用是否存活了:虽然活着但是不能处理请求【植物人】
-
应用是否就绪了:能响应请求,说明确实活的比较好
SpringBoot 事件驱动开发
-
应用启动过程生命周期事件感知(9大事件)、应用运行中事件感知(无数种)
-
事件发布:事件发布类实现
ApplicationEventPublisherAware
接口或自动注入ApplicationEventMulticaster
调用底层的事件发布方法,事件是广播发布的 -
事件监听
-
实现
ApplicationListener
接口或者是组件 + @EventListener
-
默认的监听顺序是按照全类名的字母序排列的,可以通过
@Order
注解修改优先级
-
-
-
场景模拟
.
.
-
创建事件实体【消息】
@AllArgsConstructor @Data public class UserEntity { private String userName; private String password; }
-
实现ApplicationEvent,封装事件消息
public class LoginEvent extends ApplicationEvent { public LoginEvent(UserEntity source) { super(source); } }
-
实现ApplicationEventPublisherAware接口,通过底层的事件发布者发布事件
@Service public class EventPublisher implements ApplicationEventPublisherAware { ApplicationEventPublisher applicationEventPublisher; public void sendEvent(ApplicationEvent event){ //调用底层的api发送事件 applicationEventPublisher.publishEvent(event); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { //可以获取到springboot的事件发布组件 this.applicationEventPublisher=applicationEventPublisher; } }
-
测试
@RestController public class TestPublisher { @Autowired EventPublisher eventPublisher; @RequestMapping("/test") public String testEvent(){ UserEntity userEntity = new UserEntity("3220999", "1212go12"); eventPublisher.sendEvent(new LoginEvent(userEntity)); return "ok"; } } ==========底层的事件监听器可以感知到======== 事件到达com.study.liao.boot3features.event.LoginEvent[source=UserEntity(userName=3220999, password=1212go12)]
-
使用
@EventListener
监听事件@Service public class LoginEventListener { @EventListener public void getLoginMessage(LoginEvent event){ System.out.println("感知到事件"+event); } } ========感知结果=========== 感知到事件com.study.liao.boot3features.event.LoginEvent[source=UserEntity(userName=3220999, password=1212go12)]
最佳实战
-
如果项目启动前做事使用
BootstrapRegistryInitializer
和ApplicationContextInitializer
-
如果想要在项目启动完成后做事:
ApplicationRunner
和CommandLineRunner
-
如果要干涉生命周期做事:
SpringApplicationRunListener
-
如果想要用事件机制:
ApplicationListener
自动配置原理
入门理解
自动配置流程
.
应用关注的三大核心:场景、配置、组件
-
导入
starter
场景之后会导入autoconfigure
-
寻找类路径下
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件 -
应用启动会加载所有自动配置类
xxxAutoConfiguration
-
给容器中配置功能组件
-
组件参数绑定到属性类
xxxProperties
中 -
属性类和配置文件前缀项绑定
-
@Contional
派生的条件注解进行判断是否组件生效
-
-
效果
-
修改配置文件,修改底层参数
-
所有场景自动配置好直接使用
-
可以注入SpringBoot配置好的组件随时使用
-
SPI机制【了解,回答来自ChatGPT-3.5】
-
勾吧,能不能给点官方回复
-
Java中的SPI(Service Provider Interface)是一种软件设计模式,用于在应用程序中动态地发现和加载组件
-
SPI的思想是,定义一个接口或抽象类,然后通过在classpath中定义实现该接口的类来实现对组件的动态发现和加载
-
SPI的主要目的是解决在应用程序中使用可插拔组件的问题
-
例如,一个应用程序可能需要使用不同的日志框架或数据库连接池,但是这些组件的选择可能取决于运行时的条件
-
通过使用SPI,应用程序可以在运行时发现并加载适当的组件,而无需在代码中硬编码这些组件的实现类
-
通过使用SPI,应用程序可以实现更灵活、可扩展的架构,同时也可以避免硬编码依赖关系和增加代码的可维护性
-
-
在Java中,SPI的实现方式是通过在
META-INF/services
目录下创建一个以服务接口全限定名为名字的文件,文件中包含实现该服务接口的类的全限定名-
SpringBoot的SPI机制是找
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
-
当应用程序启动时,Java的SPI机制会自动扫描classpath中的这些文件,并根据文件中指定的类名来加载实现类
-
功能开关
-
自动配置:全部都配置好,什么都不用管【自动批量导入】
-
项目一启动,spi文件中指定的所有都加载
-
-
@EnableXxxx
:手动控制哪些功能的开启【手动导入】-
开启xxx功能
-
都是利用
@Import
把此功能要用的组件导入进去
-
进阶理解
@SpringBootApplication
-
包含以下三个重要注解
-
@SpringBootConfiguration:就是@Configuration配置类,spring ioc启动就会加载创建这个类对象
-
@EnableAutoConfiguration:开启自动配置,包含以下两个重要注解
-
@AutoConfigurationPackage:扫描主程序包,加载自己的组件
-
-
利用
@Import(AutoConfigurationPackages.Registrar.class)
把主程序所在的包的所有组件导入进来 -
@Import(AutoConfigurationImportSelector.class):加载所有自动配置类,即加载starter导入的组件
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()) .getCandidates();
-
扫描SPI文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
-
-
-
@ComponentScan:组件扫描,排除前面已经扫描进来的配置类和自动配置类
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
完整启动加载流程【得配合前面的生命周期】
!!!自定义starter【看得很爽,建议跟着敲】
需求
-
场景:抽取聊天机器人场景,它可以打招呼
-
效果:任何项目导入此
starter
都具有打招呼功能,并且问候语中的人名需要可以在配置文件中修改
-
创建自定义starter项目,引入
spring-boot-starter
基础依赖 -
编写模块功能,引入模块所有需要的依赖。
-
编写
xxxAutoConfiguration
自动配置类,帮其他项目导入这个模块需要的所有组件 -
编写配置文件
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
指定启动需要加载的自动配置 -
其他项目引入即可使用
通用业务代码
-
导入配置处理器的依赖,配置文件中自定义的properties配置都会有提示
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
.
-
配置绑定
@Component @Data @ConfigurationProperties(prefix = "robot")//和配置文件的robot前缀进行绑定 public class RobotProperties { private String name; private Integer age; }
-
业务方法
@Service public class RobotService { @Autowired RobotProperties robotProperties; public String sayHello(){ return "你好"+robotProperties.getName()+"\n年龄"+robotProperties.getAge(); } }
-
测试
@RestController public class RobotController { @Autowired RobotService robotService; @RequestMapping("/robot") public String sayHello(){ return robotService.sayHello(); } }
.
基本抽取
-
创建starter项目,导入公共代码所有需要用到的依赖,然后把公共代码复制进来
.
-
此时导入该自定义场景,发现组件无法生效,因为starter所在的包和引入它的项目的主程序所在的包不是父子层级
-
在自定义starter中配置
RobotAutoConfiguration
,给容器中导入这个场景需要的所有组件【为了方便其它项目导入所需组件】-
使用
@Import
导入
@Import({RobotController.class, RobotService.class, RobotProperties.class})//导入robot功能所需要的组件 @Configuration public class RobotAutoConfiguration { }
-
使用
@Bean
导入
@Configuration public class RobotAutoConfiguration { @Bean public RobotProperties robotProperties(){ return new RobotProperties(); } //省略其它组件的注入 }
-
-
其它项目引用这个
starter
,直接导入这个RobotAutoConfiguration
就能把这个场景的组件导入进来@Import(RobotAutoConfiguration.class) @SpringBootApplication public class Boot3FeaturesApplication { public static void main(String[] args) { SpringApplication.run(Boot3FeaturesApplication.class, args); } }
-
在引入的项目中编写配置文件,测试功能是否生效
robot.name=里奥 robot.age=21
.
使用@EnableXxx机制
-
自定义starter中定义一个注解
@EnableRoot
,在注解中导入RobotAutoConfiguration
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({RobotAutoConfiguration.class}) public @interface EnableRoot { }
-
其它项目导入所需组件时直接标识
@EnableRoot
注解即可开启功能@EnableRoot @SpringBootApplication public class Boot3FeaturesApplication { public static void main(String[] args) { SpringApplication.run(Boot3FeaturesApplication.class, args); } }
完全自动配置
-
需求:导入starter时自动生效,连注解都不用标识
-
原理:依赖SpringBoot的SPI机制
-
给类资源文件夹下放一个
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
.
-
在文件中编写好自定义的自动配置类的全类名
.
-
项目启动,会自动加载自定义的自动配置类,连注解都不用写,只需要在配置文件中配置信息即可
.