快速学习 SpringBoot 看官方文档:
Spring Boot Reference Documentation
计划三天学完
笔记:https://www.yuque.com/leifengyang/springboot3
代码:https://gitee.com/leifengyang/spring-boot-3
一、SpringBoot3 - 快速入门
1.1 简介
1、前置知识
- Java17
- Spring、SpringMVC、MyBatis
- Maven、IDEA
2、环境要求
later 更新的,更后的
3、SpringBoot 是什么
创建的目的:是为了让我们更好的整合第三方框架,简化应用配置和应用创建
SpringBoot 帮我们简单、快速地创建一个独立的、生产级别的 Spring 应用(说明:SpringBoot底层是Spring)
大多数 SpringBoot 应用只需要编写少量配置即可快速整合 Spring 平台以及第三方技术
特性:
- 快速创建独立 Spring 应用
- 直接嵌入Tomcat、Jetty or Undertow(无需部署 war 包)【Servlet容器】
- (⭐)重点:提供可选的starter,简化应用整合
- (⭐)重点:按需自动配置 Spring 以及 第三方库
- 提供生产级特性:如 监控指标、健康检查、外部化配置等
- 无代码生成、无xml
总结:简化开发,简化配置,简化整合,简化部署,简化监控,简化运维。
1.2 快速体验
场景:浏览器发送/hello请求,返回"Hello,Spring Boot 3!"
1、开发流程
1. 创建项目
maven 项目
<!-- 所有springboot项目都必须继承自 spring-boot-starter-parent -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
</parent>
2.导入场景
场景启动器
<dependencies>
<!-- web开发的场景启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
3.主程序
@SpringBootApplication //这是一个SpringBoot应用
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class,args);
}
}
4.业务
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "Hello,Spring Boot 3!";
}
}
5.测试
默认启动访问: localhost:8080
6.打包 应用上线
<!-- SpringBoot应用打包插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2、新特性
1.简化整合
导入相关的场景,拥有相关的功能。场景启动器
SpringBoot 默认支持的场景启动器(⭐)
默认支持的所有场景:Developing with Spring Boot
- 官方提供的场景:命名为:
spring-boot-starter-*
- 第三方提供场景:命名为:
*-spring-boot-starter
场景一导入,万物皆就绪
2.简化开发
无需编写任何配置,直接开发业务
3.简化配置
application.properties
:
- 集中式管理配置。只需要修改这个文件就行 。
- 配置基本都有默认值
- 能写的所有配置都在: Common Application Properties
4.简化部署
打包为可执行的jar包。
linux服务器上有java环境。
5.简化运维
修改配置(外部放一个application.properties文件)、监控、健康检查。
3、Spring Initializr 创建向导 (一键创建好SpringBoot 项目)
一键创建好整个项目结构
4、 一键式创建SpringBoot 项目
1.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父项目没有管理的需要自行声明好
- 父项目没有的依赖,需要自己来导入
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
2、自动配置机制
1.初步理解
- 自动配置的 Tomcat、SpringMVC 等
- 导入场景,容器中就会自动配置好这个场景的核心组件。
- 以前: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
标注的类就是主程序类- 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. 完整流程 :程序员需要做的 导入starter
、修改配置文件,就能修改底层行为。
(1)自动配置流程细节梳理
-
导入
starter-web
:导入了web开发场景-
场景启动器导入了相关场景的所有依赖:
starter-json
、starter-tomcat
、springmvc
-
每个场景启动器都引入了一个
spring-boot-starter
,核心场景启动器。 -
核心场景启动器引入了
spring-boot-autoconfigure
包。 -
spring-boot-autoconfigure
里面囊括了所有场景的所有配置。 -
只要这个包下的所有类都能生效,那么相当于SpringBoot官方写好的整合功能就生效了。
-
SpringBoot默认却扫描不到
spring-boot-autoconfigure
下写好的所有配置类。(这些配置类给我们做了整合操作),默认只扫描主程序所在的包。
-
-
主程序:
@SpringBootApplication
-
@SpringBootApplication
由三个注解组成@SpringBootConfiguration
、@EnableAutoConfiguratio
、@ComponentScan
-
SpringBoot默认只能扫描自己主程序所在的包及其下面的子包,扫描不到
spring-boot-autoconfigure
包中官方写好的配置类 -
@EnableAutoConfiguration
:SpringBoot 开启自动配置的核心-
是由
@Import(AutoConfigurationImportSelector.class)
提供功能:批量给容器中导入组件。 -
SpringBoot启动会默认加载 142个配置类。
-
这142个配置类来自于
spring-boot-autoconfigure
下META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件指定的。- 项目启动的时候利用 @Import 批量导入组件机制把
autoconfigure
包下的142xxxxAutoConfiguration
类导入进来(自动配置类) - 虽然导入了
142
个自动配置类
- 项目启动的时候利用 @Import 批量导入组件机制把
-
-
按需生效:
- 并不是这
142
个自动配置类都能生效 - 每一个自动配置类,都有条件注解
@ConditionalOnxxx
,只有条件成立,才能生效
- 并不是这
-
-
xxxxAutoConfiguration
自动配置类 -
写业务,全程无需关心各种整合(底层这些整合写好了,而且也生效了)
(2)核心流程总结
- 导入 starter,就会导入 autoconfigure 包
- autoconfigure 包里面有一个文件
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ,
里面指定的所有启动要加载的自动配置类
@EnableAutoConfiguration 会自动的把上面文件里面写的所有 自动配置类都导入进来。
xxxAutoConfiguration 是有条件注解进行按需加载-
xxxAutoConfiguration
给容器中导入一堆组件,组件都是从xxxProperties
中提取属性值 -
xxxProperties
又是和配置文件进行了绑定
效果:导入starter
、修改配置文件,就能修改底层行为。
3. 如何学好SpringBoot
框架的框架、底层基于Spring。能调整每一个场景的底层行为。100%项目一定会用到底层自定义
摄影:
- 傻瓜:自动配置好。
- 单反:焦距、光圈、快门、感光度....
- 傻瓜+单反:
- 理解自动配置原理
- 导入starter --> 生效xxxxAutoConfiguration --> 组件 --> xxxProperties --> 配置文件
- 理解其他框架底层
- 拦截器
- 可以随时定制化任何组件
- 配置文件
- 自定义组件
(1)最佳实战:
- 选场景,导入项目
- 官方:starter
- 第三方:去仓库搜索
- 写配置,改配置文件关键项
- 数据库参数(连接地址、账号密码...)
- 分析这个场景给我们导入了哪些能用的组件
- 自动装配 这些组件进行后续使用
- 不满意 boot 提供的自动配好的默认组件
- 定制化
- 改配置
- 自定义组件
- 定制化
(2)整合 redis
- 选场景:spring-boot-starter-data-redis
- 场景AutoConfiguration 就是这个场景的自动配置类
- 写配置:
- 分析到这个场景的自动配置类开启了哪些属性绑定关系
-
@EnableConfigurationProperties(RedisProperties.class)
-
修改 redis 相关的配置
- 分析组件
- 分析到 RedisAutoConfiguration 给容器中放了 StringRedisTemplate
- 给业务代码中自动装配 StringRedisTemplate
- 定制化
- 修改配置文件
- 自定义组件,自己给容器中放一个 StringRedisTemplate
1.4 核心技能
1.4.1 常用注解
1.4.1.1 组件注册
@Configuration、@SpringBootConfiguration
@Bean、@Scope
@Controller、 @Service、@Repository、@Component
@Import
@ComponentScan
(1)步骤:
1、@Configuration 编写一个配置类
2、在配置类中,自定义方法给容器中注册组件。配合@Bean
3、或使用@Import 导入第三方的组件
1.4.1.2 条件注解
@ConditionalOnXxx
@ConditionalOnClass:如果类路径中存在这个类,则触发指定行为
@ConditionalOnMissingClass:如果类路径中不存在这个类,则触发指定行为
@ConditionalOnBean:如果容器中存在这个Bean(组件),则触发指定行为
@ConditionalOnMissingBean:如果容器中不存在这个Bean(组件),则触发指定行为
@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)
1.4.1.3 属性绑定
@ConfigurationProperties: 声明组件的属性和配置文件哪些前缀开始项进行绑定
@EnableConfigurationProperties:快速注册注解:
- 场景:SpringBoot默认只扫描自己主程序所在的包。如果导入第三方包,即使组件上标注了 @Component、@ConfigurationProperties 注解,也没用。因为组件都扫描不进来,此时使用这个注解就可以快速进行属性绑定并把组件注册进容器
更多注解参照:Spring注解驱动开发【1-26集】
1.4.2 YAML 配置文件
1.4.2.1 基本语法
- 大小写敏感
- 使用缩进表示层级关系,k: v,使用空格分割k,v
- 缩进时不允许使用Tab键,只允许使用空格。换行
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
- # 表示注释,从这个字符一直到行尾,都会被解析器忽略。
(1)YAML支持的写法:
- 对象:键值对的集合,如:映射(map)/ 哈希(hash) / 字典(dictionary)
- 数组:一组按次序排列的值,如:序列(sequence) / 列表(list)
- 纯量:单个的、不可再分的值,如:字符串、数字、bool、日期
1.4.2.2 示例
@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:
name: 小蓝
age: 3
c2: {name: 小绿,age: 2} #对象也可用{}表示
1.4.3 细节
- birthDay 推荐写为 birth-day
- 文本:
- 单引号不会转义【\n 则为普通字符串显示】
- 双引号会转义【\n会显示为换行符】
- 大文本
|
开头,大文本写在下层,保留文本格式,换行符正确显示>
开头,大文本写在下层,折叠换行符
- 多文档合并
- 使用
---
可以把多个yaml文档合并在一个文档中,每个文档区依然认为内容独立
- 使用
1.4.4 小技巧:lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>compile</scope>
</dependency>
使用 @Data
等注解
1.4.3 日志配置
感兴趣日志框架关系与起源可参考:尚硅谷SpringBoot顶尖教程(springboot之idea版spring boot)_哔哩哔哩_bilibili 视频 21~27集
1.4.3.1 简介
(1)简介
- Spring使用commons-logging作为内部日志,但底层日志实现是开放的。可对接其他日志框架。
-
- spring5及以后 commons-logging被spring直接自己写了。
-
- 支持 jul,log4j2,logback。SpringBoot 提供了默认的控制台输出配置,也可以配置输出为文件。
- logback是默认使用的。
- 虽然日志框架很多,但是我们不用担心,使用 SpringBoot 的默认配置就能工作的很好。
(2)SpringBoot怎么把日志默认配置好的
1、每个 starter
场景,都会导入一个核心场景 spring-boot-starter
2、核心场景引入了日志的所用功能 spring-boot-starter-logging
3、默认使用了 logback + slf4j
组合作为默认底层日志
4、日志是系统一启动就要用
,xxxAutoConfiguration
是系统启动好了以后放好的组件,后来用的。
5、日志是利用监听器机制配置好的。ApplicationListener
。
6、日志所有的配置都可以通过修改配置文件实现。以 logging
开始的所有配置。
1.4.3.2 日志格式
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]
(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}
可修改为:
'%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{15} ===> %msg%n'
1.4.3.3 记录日志
Logger logger = LoggerFactory.getLogger(getClass());
或者使用Lombok的@Slf4j注解
1.4.3.4 日志级别
- 由低到高:
ALL,TRACE, DEBUG, INFO, WARN, ERROR,FATAL,OFF
;- 只会打印指定级别及以上级别的日志
-
- ALL:打印所有日志
- TRACE:追踪框架详细流程日志,一般不使用
- DEBUG:开发调试细节日志
- INFO:关键、感兴趣信息日志
- WARN:警告但不是错误的信息日志,比如:版本过时
- ERROR:业务错误日志,比如出现各种异常
- FATAL:致命错误日志,比如jvm系统崩溃
- OFF:关闭所有日志记录
- 不指定级别的所有类,都使用 root 指定的级别作为默认级别
- SpringBoot日志默认级别是 INFO
1.4.3.5 日志分组
(1)比较有用的技巧是:
将相关的logger分组在一起,统一配置。SpringBoot 也支持。比如:Tomcat 相关的日志统一设置
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat
logging.level.tomcat=trace
(2)SpringBoot 预定义两个组
1.4.3.6 文件输出
1.4.3.7 文件归档和滚动切割
归档:每天的日志单独存到一个文档中。
切割:每个文件10MB,超过大小切割成另外一个文件。
- 每天的日志应该独立分割出来存档。如果使用logback(SpringBoot 默认整合),可以通过application.properties/yaml文件指定日志滚动规则。
- 如果是其他日志系统,需要自行配置(添加log4j2.xml或log4j2-spring.xml)
- 支持的滚动规则设置如下
1.4.3.8 自定义配置
通常我们配置 application.properties 就够了。当然也可以自定义。比如:
1.4.3.9 切换日志组合
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
(1)log4j2支持yaml和json格式的配置文件
1.4.3.10 最佳实战
- 导入任何第三方框架,先排除它的日志包,因为Boot底层控制好了日志
- 修改
application.properties
配置文件,就可以调整日志的所有行为。如果不够,可以编写日志框架自己的配置文件放在类路径下就行,比如logback-spring.xml
,log4j2-spring.xml
- 如需对接专业日志系统,也只需要把 logback 记录的日志灌倒 kafka之类的中间件,这和SpringBoot没关系,都是日志框架自己的配置,修改配置文件即可
- 业务中使用slf4j-api记录日志。不要再 sout 了
二、SpringBoot3 - Web 开发
2.0. WebMvcAutoConfiguration原理
1、生成条件
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class }) //在这些自动配置之后
@ConditionalOnWebApplication(type = Type.SERVLET) //如果是web应用就生效,类型SERVLET、REACTIVE 响应式web
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) //容器中没有这个Bean,才生效。默认就是没有
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)//优先级
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration {
}
2、效果
- 放了两个Filter:
-
HiddenHttpMethodFilter
;页面表单提交Rest请求(GET、POST、PUT、DELETE) FormContentFilter
: 表单内容Filter,GET(数据放URL后面)、POST(数据放请求体)请求可以携带数据,PUT、DELETE 的请求体数据会被忽略
-
- 给容器中放了
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{
}
3、WebMvcConfigurer 接口
提供了配置SpringMVC底层的所有组件入口
4、静态资源规则源码
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
//1、
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 导入依赖
- 规则二:访问:
/**
路径就去静态资源默认的四个位置找资源
- 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
,如果服务这个资源没有发生变化,下次访问的时候就可以直接让浏览器用自己缓存中的东西,而不用给服务器发请求。
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
5、EnableWebMvcConfiguration 源码
//SpringBoot 给容器中放 WebMvcConfigurationSupport 组件。
//我们如果自己放了 WebMvcConfigurationSupport 组件,Boot的WebMvcAutoConfiguration都会失效。
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware
{
}
HandlerMapping
: 根据请求路径/a
找那个handler能处理请求WelcomePageHandlerMapping
:- 访问
/**
路径下的所有请求,都在以前四个静态资源路径下找,欢迎页也一样 - 找
index.html
:只要静态资源的位置有一个index.html
页面,项目启动默认访问
- 访问
6、为什么容器中放一个 WebMvcConfigurer 就能配置底层行为
- WebMvcAutoConfiguration 是一个自动配置类,它里面有一个
EnableWebMvcConfiguration
EnableWebMvcConfiguration
继承与DelegatingWebMvcConfiguration
,这两个都生效DelegatingWebMvcConfiguration
利用 DI 把容器中 所有WebMvcConfigurer
注入进来- 别人调用 `
DelegatingWebMvcConfiguration
` 的方法配置底层规则,而它调用所有WebMvcConfigurer
的配置底层方法。
7、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);
2.1. Web场景
2.1.1 自动配置
1、整合web场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2、引入了 autoconfigure
功能
3、@EnableAutoConfiguration
注解使用@Import(AutoConfigurationImportSelector.class)
批量导入组件
4、加载 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中配置的所有组件
5、所有自动配置类如下
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
6、绑定了配置文件的一堆配置项
- 1、SpringMVC的所有配置
spring.mvc
- 2、Web场景通用配置
spring.web
- 3、文件上传配置
spring.servlet.multipart
- 4、服务器的配置
server
: 比如:编码方式
2.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.2. 静态资源
2.2.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;
2、静态资源缓存
如前面所述
- 所有静态资源都定义了缓存规则。【浏览器访问过一次,就会缓存一段时间】,但此功能参数无默认值
- period: 缓存间隔。 默认 0S;
- cacheControl:缓存控制。 默认无;
- useLastModified:是否使用lastModified头。 默认 false
3、欢迎页
欢迎页规则在 WebMvcAutoConfiguration 中进行了定义:
- 在静态资源目录下找 index.html
- 没有就在 templates下找index模板页
4、Favicon
- 在静态资源目录下找 favicon.ico
5、缓存实验
Properties
server.port=9000
#1、spring.web:
# 1.配置国际化的区域信息
# 2.静态资源策略(开启、处理链、缓存)
#开启静态资源映射规则
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
2.2.2 自定义静态资源规则
1、配置方式
spring.mvc
: 静态资源访问前缀路径
spring.web
:
- 静态资源目录
- 静态资源缓存策略
Properties
#1、spring.web:
# 1.配置国际化的区域信息
# 2.静态资源策略(开启、处理链、缓存)
#开启静态资源映射规则
spring.web.resources.add-mappings=true
#设置缓存
spring.web.resources.cache.period=3600
##缓存详细合并项控制,覆盖period配置:
## 浏览器第一次请求服务器,服务器告诉浏览器此资源缓存7200秒,7200秒以内的所有此资源访问不用发给服务器请求,7200秒以后发请求给服务器
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/
#2、 spring.mvc
## 2.1. 自定义webjars路径前缀
spring.mvc.webjars-path-pattern=/wj/**
## 2.2. 静态资源访问路径前缀
spring.mvc.static-path-pattern=/static/**
2、代码方式
- 容器中只要有一个 WebMvcConfigurer 组件。配置的底层行为都会生效
- @EnableWebMvc //禁用boot的默认配置
@Configuration //这是一个配置类
public class MyConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//保留以前规则
//自己写新的规则。
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/a/","classpath:/b/")
.setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));
}
}
@Configuration //这是一个配置类,给容器中放一个 WebMvcConfigurer 组件,就能自定义底层
public class MyConfig /*implements WebMvcConfigurer*/ {
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/a/", "classpath:/b/")
.setCacheControl(CacheControl.maxAge(1180, TimeUnit.SECONDS));
}
};
}
}
2.3. 路径匹配
1、Ant 风格路径用法
Ant 风格的路径模式语法具有以下规则:
- *:表示任意数量的字符。
- ?:表示任意一个字符。
- **:表示任意数量的目录。
- {}:表示一个命名的模式占位符。
- []:表示字符集合,例如[a-z]表示小写字母。
例如:
- *.html 匹配任意名称,扩展名为.html的文件。
- /folder1/*/*.java 匹配在folder1目录下的任意两级目录下的.java文件。
- /folder2/**/*.jsp 匹配在folder2目录下任意目录深度的.jsp文件。
- /{type}/{id}.html 匹配任意文件名为{id}.html,在任意命名的{type}目录下的文件。
注意:Ant 风格的路径模式语法中的特殊字符需要转义,如:
- 要匹配文件路径中的星号,则需要转义为\\*。
- 要匹配文件路径中的问号,则需要转义为\\?。
2、模式切换
@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;
}
2.4. 内容协商
2.4.1 多端内容适配
1、默认规则
- SpringBoot 多端内容适配。
- 基于请求头内容协商:(默认开启)
- 客户端向服务端发送请求,携带HTTP标准的Accept请求头。
- Accept:
application/json
、text/xml
、text/yaml
- 服务端根据客户端请求头期望的数据类型进行动态返回
- Accept:
- 客户端向服务端发送请求,携带HTTP标准的Accept请求头。
- 基于请求参数内容协商:(需要开启)
- 发送请求 GET /projects/spring-boot?format=json
- 匹配到 @GetMapping("/projects/spring-boot")
- 根据参数协商,优先返回 json 类型数据【需要开启参数匹配设置】
- 发送请求 GET /projects/spring-boot?format=xml,优先返回 xml 类型数据
- 基于请求头内容协商:(默认开启)
2、效果演示
请求同一个接口,可以返回json和xml不同格式数据
- 引入支持写出xml内容依赖
- 标注注解
- 开启基于请求参数的内容协商
- 效果
3、配置协商规则与支持类型
- 修改内容协商方式
- Properties
#使用参数进行内容协商 spring.mvc.contentnegotiation.favor-parameter=true #自定义参数名,默认为format spring.mvc.contentnegotiation.parameter-name=myparam
- Properties
- 大多数 MediaType 都是开箱即用的。也可以自定义内容类型,如:
- Properties
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
- Properties
2.4.2 自定义内容返回
1、增加yaml返回支持
导入依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
把对象写出成YAML
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);
}
编写配置 Properties
#新增一种媒体类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
增加HttpMessageConverter
组件,专门负责把对象写出为yaml格式
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override //配置一个能把对象转为yaml的messageConverter
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MyYamlHttpMessageConverter());
}
};
}
2、思考:如何增加其他
- 配置媒体类型支持:
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
- 编写对应的
HttpMessageConverter
,要告诉Boot这个支持的媒体类型- 按照3的示例
- 把MessageConverter组件加入到底层
- 容器中放一个`
WebMvcConfigurer
` 组件,并配置底层的MessageConverter
- 容器中放一个`
3、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);
}
}
}
2.4.3 内容协商原理
HttpMessageConverter
怎么工作?合适工作?- 定制
HttpMessageConverter
来实现多端内容协商- 编写
WebMvcConfigurer
提供的configureMessageConverters
底层,修改底层的MessageConverter
1、@ResponseBody
由HttpMessageConverter
处理
标注了
@ResponseBody
的返回值 将会由支持它的HttpMessageConverter
写给浏览器
- 如果controller方法的返回值标注了
@ResponseBody
注解- 请求进来先来到
DispatcherServlet
的doDispatch()
进行处理 - 找到一个
HandlerAdapter
适配器。利用适配器执行目标方法 RequestMappingHandlerAdapter
来执行,调用invokeHandlerMethod()
来执行目标方法- 目标方法执行之前,准备好两个东西
HandlerMethodArgumentResolver
:参数解析器,确定目标方法每个参数值HandlerMethodReturnValueHandler
:返回值处理器,确定目标方法的返回值改怎么处理
RequestMappingHandlerAdapter
里面的invokeAndHandle()
真正执行目标方法- 目标方法执行完成,会返回返回值对象
- 找到一个合适的返回值处理器
HandlerMethodReturnValueHandler
- 最终找到
RequestResponseBodyMethodProcessor
能处理 标注了@ResponseBody
注解的方法 RequestResponseBodyMethodProcessor
调用writeWithMessageConverters
,利用MessageConverter
把返回值写出去
- 请求进来先来到
上面解释:
@ResponseBody
由HttpMessageConverter
处理
HttpMessageConverter
会先进行内容协商- 遍历所有的
MessageConverter
看谁支持这种内容类型的数据 - 默认
MessageConverter
有以下 - 最终因为要
json
所以MappingJackson2HttpMessageConverter
支持写出json - jackson用
ObjectMapper
把对象写出去
- 遍历所有的
2、WebMvcAutoConfiguration
提供几种默认HttpMessageConverters
EnableWebMvcConfiguration
通过addDefaultHttpMessageConverters
添加了默认的MessageConverter
;如下:ByteArrayHttpMessageConverter
: 支持字节数据读写StringHttpMessageConverter
: 支持字符串读写ResourceHttpMessageConverter
:支持资源读写ResourceRegionHttpMessageConverter
: 支持分区资源写出AllEncompassingFormHttpMessageConverter
:支持表单xml/json读写MappingJackson2HttpMessageConverter
: 支持请求响应体Json读写
2.5. 模板引擎
- 由于 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>
2.5.1 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
的页面
- 所有的模板页面在
2.5.2 基础语法
1、核心用法
th:xxx
:动态渲染指定的 html 标签属性值、或者th指令(遍历、判断等)
th:text
:标签体内文本值渲染
-
th:utext
:不会转义,显示为html原本的样子。
th:属性
:标签指定属性渲染th:attr
:标签任意属性渲染th:if
th:each
...
:其他th指令- 例如:
<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生成工具
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)
特殊语法:
- 无操作:_
所有以上都可以嵌套组合
Plain Text
'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
2.5.3 属性设置
- 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}" />
2.5.4 遍历
语法:
th:each="元素名,迭代状态 : ${集合}"
<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>
iterStat 有以下属性:
- index:当前遍历元素的索引,从0开始
- count:当前遍历元素的索引,从1开始
- size:需要遍历元素的总数量
- current:当前正在遍历的元素对象
- even/odd:是否偶数/奇数行
- first:是否第一个元素
- last:是否最后一个元素
2.5.5 判断
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>
2.5.6 属性优先级
- 片段
- 遍历
- 判断
<ul>
<li th:each="item : ${items}" th:text="${item.description}">Item description here...</li>
</ul>
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 |
2.5.7 行内写法
[[...]] or [(...)]
<p>Hello, [[${session.user.name}]]!</p>
2.5.8 变量选择
<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
2.5.9 模板布局
- 定义模板:
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>
2.5.10 devtools
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
修改页面后;ctrl+F9
刷新效果;
java代码的修改,如果devtools
热启动了,可能会引起一些bug,难以排查
2.6. 国际化
实现步骤:
- 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;
}
2.7. 错误处理
2.7.1 默认机制
错误处理的自动配置都在
ErrorMvcAutoConfiguration
中,两大核心机制:
- 1. SpringBoot 会自适应处理错误,响应页面或JSON数据
- 2. SpringMVC的错误处理机制依然保留,MVC处理不了,才会交给boot进行处理
1、发生错误以后,转发给/error路径,SpringBoot在底层写好一个 BasicErrorController的组件,专门处理这个请求
@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);
}
2、错误页面是这么解析到的
//1、解析错误的自定义视图地址
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
//2、如果解析不到错误页面的地址,默认的错误页就是 error
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
3、容器中专门有一个错误视图解析器
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}
4、SpringBoot解析自定义错误页的默认规则
@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;
}
5、容器中有一个默认的名为 error 的 view; 提供了默认白页功能
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
6、封装了JSON格式的错误信息
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
7、规则:
- 解析一个错误页
- 如果发生了500、404、503、403 这些错误
- 如果有模板引擎,默认在
classpath:/templates/error/精确码.html
- 如果没有模板引擎,在静态资源文件夹下找
精确码.html
- 如果有模板引擎,默认在
- 如果匹配不到
精确码.html
这些精确的错误页,就去找5xx.html
,4xx.html
模糊匹配- 如果有模板引擎,默认在
classpath:/templates/error/5xx.html
- 如果没有模板引擎,在静态资源文件夹下找
5xx.html
- 如果有模板引擎,默认在
- 如果发生了500、404、503、403 这些错误
- 如果模板引擎路径
templates
下有error.html
页面,就直接渲染
2.7.2 自定义错误响应
1. 自定义json响应
使用@ControllerAdvice + @ExceptionHandler 进行统一异常处理
2. 自定义页面响应
根据boot的错误页面规则,自定义页面模板
2.7.3 最佳实战
- 前后分离
- 后台发生的所有错误,
@ControllerAdvice + @ExceptionHandler
进行统一异常处理。
- 后台发生的所有错误,
- 服务端页面渲染
- 不可预知的一些,HTTP码表示的服务器或客户端错误
- 给
classpath:/templates/error/
下面,放常用精确的错误码页面。500.html
,404.html
- 给
classpath:/templates/error/
下面,放通用模糊匹配的错误码页面。5xx.html
,4xx.html
- 给
- 不可预知的一些,HTTP码表示的服务器或客户端错误
- 发生业务错误
- 核心业务,每一种错误,都应该代码控制,跳转到自己定制的错误页。
- 通用业务,
classpath:/templates/error.html
页面,显示错误信息。
【页面,JSON,可用的Model数据如下】
2.8. 嵌入式容器
2.8.1 自动配置原理
- SpringBoot 默认嵌入Tomcat作为Servlet容器。
- 自动配置类是
ServletWebServerFactoryAutoConfiguration
,EmbeddedWebServerFactoryCustomizerAutoConfiguration
- 自动配置类开始分析功能。`xxxxAutoConfiguration`
@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
都有条件注解。系统中有这个类才行(也就是导了包) - 默认
Tomcat
配置生效。给容器中放 TomcatServletWebServerFactory - 都给容器中
ServletWebServerFactory
放了一个 web服务器工厂(造web服务器的) - web服务器工厂 都有一个功能,
getWebServer
获取web服务器 - TomcatServletWebServerFactory 创建了 tomcat。
- 导入
- ServletWebServerFactory 什么时候会创建 webServer出来。
ServletWebServerApplicationContext
ioc容器,启动的时候会调用创建web服务器- Spring容器刷新(启动)的时候,会预留一个时机,刷新子容器。
onRefresh()
- refresh() 容器刷新 十二大步的刷新子容器会调用
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。
2.8.2 自定义
切换服务器;
<properties>
<servlet-api.version>3.1.0</servlet-api.version>
</properties>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<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>
2.8.3 最佳实践
用法:
- 修改
server
下的相关配置就可以修改服务器参数 - 通过给容器中放一个
ServletWebServerFactory
,来禁用掉SpringBoot默认放的服务器工厂,实现自定义嵌入任意服务器。
2.9. 全面接管 SpringMVC
- SpringBoot 默认配置好了 SpringMVC 的所有常用特性。
- 如果我们需要全面接管SpringMVC的所有配置并禁用默认配置,仅需要编写一个
WebMvcConfigurer
配置类,并标注@EnableWebMvc
即可- 全手动模式
@EnableWebMvc
: 禁用默认配置WebMvcConfigurer
组件:定义MVC的底层行为
2.9.1 WebMvcAutoConfiguration 到底自动配置了哪些规则
SpringMVC自动配置场景给我们配置了如下所有默认行为
WebMvcAutoConfiguration
web场景的自动配置类- 支持RESTful的filter:HiddenHttpMethodFilter
- 支持非POST请求,请求体携带数据:FormContentFilter
- 导入
EnableWebMvcConfiguration
:RequestMappingHandlerAdapter
ContentNegotiationManager
:内容协商管理器WebBindingInitializer
:请求参数的封装与绑定Validator
: 数据校验JSR303
提供的数据校验功能FormattingConversionService
: 数据格式化 、类型转化FlashMapManager
:临时数据共享ThemeResolver
:主题解析器LocaleResolver
:国际化解析器ExceptionHandlerExceptionResolver
:默认的异常解析器RequestMappingHandlerMapping
:找每个请求由谁处理的映射关系WelcomePageHandlerMapping
: 欢迎页功能支持(模板引擎目录、静态资源目录放index.html),项目访问/ 就默认展示这个页面.
WebMvcAutoConfigurationAdapter
配置生效,它是一个WebMvcConfigurer
,定义mvc底层组件- 定义好
WebMvcConfigurer
底层组件默认功能;所有功能详见列表 ProblemDetailsExceptionHandler
:错误详情- 静态资源链规则
- 请求上下文过滤器:
RequestContextFilter
: 任意位置直接获取当前请求 - 内容协商解析器:
ContentNegotiatingViewResolver
- 视图解析器:
BeanNameViewResolver
,视图名(controller方法的返回值字符串)就是组件名 - 视图解析器:
InternalResourceViewResolver
- SpringMVC内部场景异常被它捕获:
- 定义好
- 定义了MVC默认的底层行为:
WebMvcConfigurer
2.9.2 @EnableWebMvc 禁用默认行为
@EnableWebMvc
给容器中导入DelegatingWebMvcConfiguration
组件,他是WebMvcConfigurationSupport
WebMvcAutoConfiguration
有一个核心的条件注解,@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
,容器中没有WebMvcConfigurationSupport
,WebMvcAutoConfiguration
才生效.- @EnableWebMvc 导入
WebMvcConfigurationSupport
导致WebMvcAutoConfiguration
失效。导致禁用了默认行为
2.9.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 业务逻辑的直接视图页渲染 | 无 |
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 个 |
getMessageCodesResolver | 无 | 消息码解析器:国际化使用 | 无 |
2.10. 最佳实践
SpringBoot 已经默认配置好了Web开发场景常用功能。我们直接使用即可
2.10.1 三种方式
【总结】:
给容器中写一个配置类@Configuration
实现 WebMvcConfigurer
但是不要标注 @EnableWebMvc
注解,实现手自一体的效果。
2.10.2 两种模式
1、前后分离模式
: @RestController
响应JSON数据
2、前后不分离模式
:@Controller + Thymeleaf模板引擎
2.11. Web新特性
2.11.1 Problemdetails
RFC 7807: RFC 7807: Problem Details for HTTP APIs
错误信息返回新格式
1、原理
@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();
}
}
ProblemDetailsExceptionHandler
是一个@ControllerAdvice
集中处理系统异常- 处理以下异常。如果系统出现以下异常,会被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
})
2、效果:
默认响应错误的json。状态码 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 org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:265)\r\n\tat org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:441)\r\n\tat org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:382)\r\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:126)\r\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:68)\r\n\tat org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:505)\r\n\tat org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1275)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1057)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:563)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:631)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:833)\r\n",
"message": "Method 'POST' is not supported.",
"path": "/list"
}
3、开启ProblemDetails返回, 使用新的MediaType
Content-Type: application/problem+json
+ 额外扩展返回
JSON
{
"type": "about:blank",
"title": "Method Not Allowed",
"status": 405,
"detail": "Method 'POST' is not supported.",
"instance": "/list"
}
2.11.2 函数式Web
SpringMVC 5.2
以后 允许我们使用函数式的方式,定义Web的请求处理流程。函数式接口
Web请求处理的方式:
@Controller + @RequestMapping
:耦合式 (路由、业务耦合)- 函数式Web:分离式(路由、业务分离)
1、场景
场景:User RESTful - CRUD
- GET /user/1 获取1号用户
- GET /users 获取所有用户
- POST /user 请求体携带JSON,新增一个用户
- PUT /user/1 请求体携带JSON,修改1号用户
- DELETE /user/1 删除1号用户
2、核心类
- RouterFunction
- RequestPredicate
- ServerRequest
- ServerResponse
3、示例
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()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build();
}
}
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();
}
}