SpringBoot3-Web开发
# Web 自动装配原理
SpringBoot 的 WEB 开发能力,由 SpringMVC 提供。
WebMvcAutoConfiguration 类负责了 SpringMVC 的自动装配能力
@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)
2
3
4
5
6
7
# Filter
类中放了两个 Filter:
HiddenHttpMethodFilter
;页面表单提交 Rest 请求(GET、POST、PUT、DELETE)FormContentFilter
: 表单内容 Filter,GET(数据放 URL 后面)、POST(数据放请求体)请求可以携带数据,PUT、DELETE 的请求体数据会被忽略
# 自动装配
WebMvcAutoConfigurationAdapter
,它的目的是提供 Spring Boot 自动配置的默认行为,包括注册必要的 Spring MVC 基础设施组件,如视图解析器、消息转换器等。它实现了 WebMvcConfigurer
接口,允许开发者通过扩展这个类来自定义 Spring MVC 的配置。
# 手动配置
在 WebMvcAutoConfiguration
类中有个内部类为 EnableWebMvcConfiguration
。这个 EnableWebMvcConfiguration
提供了所有 @EnableWebMvc 注解的实现,允许开发者完全控制 Spring MVC 的配置,但是一旦配置了开启了 @EnableWebMvc 注解,那么 WebMvcAutoConfiguration
类中的行为就失效了即丧失了 Spring Boot 的一些自动配置特性,通常用于需要覆盖或增强自动配置提供的默认行为的场景。
// 因为EnableWebMvcConfiguration继承了DelegatingWebMvcConfiguration,而DelegatingWebMvcConfiguration又是WebMvcConfigurationSupport的子类,而在WebMvcAutoConfiguration类上有注解@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)即容器中不存在WebMvcConfigurationSupport时WebMvcAutoConfiguration才会生效
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
}
2
3
注意
所谓 “手动” 并非指的是完全需要开发者自己创建和配置 bean,或者明确地在配置类中定义各种组件。而是需要自定义 Spring MVC 的部分配置。
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
// 配置静态资源处理
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
// 配置视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp");
}
// 其他自定义的 Spring MVC 配置
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 为什么 WebMvcConfigurer 能配置底层行为?
- WebMvcAutoConfiguration 是一个自动配置类,它里面有一个 EnableWebMvcConfiguration
- EnableWebMvcConfiguration 继承与 DelegatingWebMvcConfiguration,这两个都生效
- DelegatingWebMvcConfiguration 利用 DI 把容器中 所有 WebMvcConfigurer 注入进来
- 别人调用
DelegatingWebMvcConfiguration
的方法配置底层规则,而它调用所有 WebMvcConfigurer 的配置底层方法。
# WebMvcConfigurationSupport
WebMvcConfigurationSupport
是 Spring MVC 中一个非常重要的类,它在 Spring MVC 的配置中扮演着核心角色。它负责设置和管理 Spring MVC 的各种组件,如处理器映射、视图解析器、参数解析器等。提供了很多的默认设置。判断系统中是否有相应的类:如果有,就加入相应的 HttpMessageConverter
。
# 全面接管 SpringMVC
SpringBoot 默认配置好了 SpringMVC 的所有常用特性。
如果我们需要全面接管 SpringMVC 的所有配置并禁用默认配置,仅需要编写一个
WebMvcConfigurer
配置类,并标注@EnableWebMvc
即可全手动模式
@EnableWebMvc
: 禁用默认配置
WebMvcConfigurer 组件:定义 MVC 的底层行为
# WebMvcAutoConfiguration 到底自动配置了哪些规则
支持 RESTful 的 filter:HiddenHttpMethodFilter
支持非 POST 请求,请求体携带数据:FormContentFilter
导入 EnableWebMvcConfiguration:
RequestMappingHandlerAdapter
WelcomePageHandlerMapping
: 欢迎页功能支持(模板引擎目录、静态资源目录放 index.html),项目访问 / 就默认展示这个页面.RequestMappingHandlerMapping
:找每个请求由谁处理的映射关系ExceptionHandlerExceptionResolver
:默认的异常解析器LocaleResolver
:国际化解析器ThemeResolver
:主题解析器FlashMapManager
:临时数据共享FormattingConversionService
: 数据格式化 、类型转化Validator
: 数据校验JSR303
提供的数据校验功能WebBindingInitializer
:请求参数的封装与绑定ContentNegotiationManager
:内容协商管理器
WebMvcAutoConfigurationAdapter 配置生效,它是一个 WebMvcConfigurer
,定义 mvc 底层组件
- 定义好
WebMvcConfigurer
底层组件默认功能;所有功能详见列表 - 视图解析器:
InternalResourceViewResolver
- 视图解析器:
BeanNameViewResolver
,** 视图名(controller 方法的返回值字符串)** 就是组件名 - 内容协商解析器:
ContentNegotiatingViewResolver
- 请求上下文过滤器:
RequestContextFilter
: 任意位置直接获取当前请求 - 静态资源链规则
ProblemDetailsExceptionHandler
:错误详情,SpringMVC 内部场景异常被它捕获:
定义了 MVC 默认的底层行为: WebMvcConfigurer
# @EnableWebMvc 禁用默认行为
@EnableWebMvc
给容器中导入 DelegatingWebMvcConfiguration
组件,为 WebMvcConfigurationSupport
的子类。
WebMvcAutoConfiguration
有一个核心的条件注解, @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
,容器中没 WebMvcConfigurationSupport
, WebMvcAutoConfiguration
才生效.
@EnableWebMvc 导入 WebMvcConfigurationSupport
导致 WebMvcAutoConfiguration
失效。导致禁用了默认行为
- @EnableWebMVC 禁用了 Mvc 的自动配置
- WebMvcConfigurer 定义 SpringMVC 底层组件的功能类
# 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 | 无 | 消息码解析器:国际化使用 | 无 |
# 总结
# 三种方式
方式 | 用法 | 效果 | |
---|---|---|---|
全自动 | 直接编写控制器逻辑 | 全部使用自动配置默认效果 | |
手自一体 | @Configuration + 配置 WebMvcConfigurer + 配置 WebMvcRegistrations | 不要标注 @EnableWebMvc | 保留自动配置效果 手动设置部分功能 定义 MVC 底层组件 |
全手动 | @Configuration + 配置 WebMvcConfigurer | 标注 @EnableWebMvc | 禁用自动配置效果 全手动设置 |
总结:给容器中写一个配置类 @Configuration
实现 WebMvcConfigurer
但是不要标注 @EnableWebMvc
注解,实现手自一体的效果。
# 两种模式
- 前后分离模式:
@RestController
响应 JSON 数据 - 前后不分离模式:
@Controller
+Thymeleaf模板引擎
# 静态资源
# 默认规则
# 静态资源映射
addResourceHandlers
是处理静态资源规则的方法,开发者可以通过重写这个方法来实现自定义静态资源处理
规则一:访问 /webjars/**
路径就去 classpath:/META-INF/resources/webjars/
下找资源.
WebJars (opens new window) 是一个很神奇的东西,可以让以 jar 包的形式来使用前端的各种框架、组件。
规则二:访问 /** 路径就去静态资源默认的四个位置
classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/ ` 找资源。这些都是源码中写死的。
# 静态资源缓存
静态资源默认都有缓存规则的设置
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern,
Consumer<ResourceHandlerRegistration> customizer) {
if (registry.hasMappingForPattern(pattern)) {
return;
}
ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
customizer.accept(registration);
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
customizeResourceHandlerRegistration(registration);
}
2
3
4
5
6
7
8
9
10
11
12
- 所有缓存的设置,直接通过配置文件:
spring.web
。 - cachePeriod: 缓存周期; 多久不用找服务器要新的。 默认没有,以 s 为单位。
- cacheControl: HTTP 缓存控制。
- useLastModified:是否使用最后一次修改。配合 HTTP Cache 规则。
# 测试
#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
3
4
5
6
7
8
9
10
11
12
13
14
然后在对应的 static 或者 public 目录中放一个图片或者其他的静态资源,通过浏览器访问就能看到,第二次访问的时候返回的就是 304 状态码了。你好我什么
# 欢迎页
欢迎页规则在 WebMvcAutoConfiguration 中进行了定义:
- 在静态资源目录下找 index.html。
- 没有就在 templates 下找 index 模板页。
# Favicon
在静态资源目录下找 favicon.ico
# 自定义静态资源规则
自定义静态资源路径、自定义缓存规则
# 配置方式
spring.mvc
: 静态资源访问前缀路径
spring.web
:
- 静态资源目录
- 静态资源缓存策略
#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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 代码方式
- 容器中只要有一个 WebMvcConfigurer 组件。配置的底层行为都会生效
- @EnableWebMvc // 禁用 boot 的默认配置
//方式一
@Configuration
@EnableWebMvc
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));
}
}
2
3
4
5
6
7
8
9
10
11
12
//方式二
@Configuration //这是一个配置类,给容器中放一个 WebMvcConfigurer 组件,就能自定义底层
@EnableWebMvc
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(1180, TimeUnit.SECONDS));
}
};
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 路径匹配
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 风格的路径模式语法中的特殊字符需要转义,如:
- 要匹配文件路径中的星号,则需要转义为
\\*
。 - 要匹配文件路径中的问号,则需要转义为
\\?
。
@GetMapping("/a*/ti*b/{fe:[g-s]+}/**")
public String test01(HttpServletRequest request,
@PathVariable("fe") String path){
log.info("路径变量p1:{}", path);
//获取请求路径
String uri = request.getRequestURI();
return uri;
}
2
3
4
5
6
7
8
# 切换模式
SpringMVC 中默认使用的是 AntPathMatcher 这种匹配方式
AntPathMatcher 与 PathPatternParser
- PathPatternParser 在 jmh 基准测试下,有 6~8 倍吞吐量提升,降低 30%~40% 空间分配率
- PathPatternParser 兼容 AntPathMatcher 语法,并支持更多类型的路径模式
- PathPatternParser "*" 多段匹配的支持仅允许在模式末尾使用 *
首先需要再配置文件中进行配置
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
@GetMapping("/a*/**/ti*b/{fe:[g-s]+}/**")
public String test01(HttpServletRequest request,
@PathVariable("fe") String path){
//...........
}
2
3
4
5
# 内容协议

# 多端内容适配
# 默认规则
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 类型数据
# 测试
## 开启基于请求参数的内容协商功能。 默认参数名:format。 默认此功能不开启
spring.mvc.contentnegotiation.favor-parameter=true
## 指定内容协商时使用的参数名。默认是 format,可以修改
spring.mvc.contentnegotiation.parameter-name=type
2
3
4
spring.mvc.contentnegotiation.favor-parameter=true
- 这个配置启用了基于请求参数的内容协商策略。
- 当设置为 true 时,Spring MVC 将允许通过请求参数来指定期望的响应格式。
- 这意味着客户端可以通过在 URL 中添加一个参数来请求特定格式的响应。
spring.mvc.contentnegotiation.parameter-name=type
- 这个配置指定了用于内容协商的请求参数名。
- 在这个例子中,参数名被设置为 "type"。
- 这意味着客户端可以使用 "type" 参数来指定期望的响应格式。
@JacksonXmlRootElement // 可以写出为xml文档
@Data
public class Person {
private Long id;
private String userName;
private String email;
private Integer age;
}
2
3
4
5
6
7
8
@GetMapping("person")
public Person getPerson() {
Person person = new Person();
person.setId(1L);
person.setAge(20);
person.setUserName("东东");
person.setEmail("151561561@qq.com");
return person;
}
2
3
4
5
6
7
8
9
通过访问 http://localhost:8080/person?type=xml
即可访问。
# 原理 - HttpMessageConverter
对于类型返回的处理主要是通过注解 @ResponseBody
来处理,而 @ResponseBody
由 HttpMessageConverter
处理。SpringMVC 的核心为 DispatcherServlet
类中的 doDispatch 方法。
# @ResponseBody
由 HttpMessageConverter
处理
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//.........................................
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //寻找合适的是适配器
//.........................................
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//调用适配器的处理方法
}
2
3
4
5
6
7
8
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
//.........................................
mav = invokeHandlerMethod(request, response, handlerMethod);//调用处理方法
//.........................................
return mav;
}
2
3
4
5
6
7
8
9
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//.........................................
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);// 参数解析器,确定目标方法每个参数值,放的是全部的参数解析器
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);//返回值处理器,确定目标方法的返回值改怎么处理,同样也是放的全部返回值处理器
}
//.........................................
invocableMethod.invokeAndHandle(webRequest, mavContainer);//处理方法,会在方法中找到符合的返回值处理器
}
2
3
4
5
6
7
8
9
10
11
12
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//.........................................
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
//.........................................
}
2
3
4
5
6
7
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//.........................................
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); //找到合适返回值处理器,RequestResponseBodyMethodProcessor
//.........................................
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);//处理返回值
}
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
if (handler.supportsReturnType(returnType)) {//通过这个方法就可以获取到合适的处理器
return handler;
}
}
return null;
}
//可以看到对@ResponseBody的处理
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
//这个方法就是处理返回值的逻辑
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
2
3
4
5
6
# 处理请求头中 Content-Type
在 writeWithMessageConverters 方法中获取请求头中的需要的返回值类型 selectedMediaType
,遍历所有的 MessageConverter
看谁支持这种内容类型的数据,最后找到 MappingJackson2XmlHttpMessageConverter 这个类进行处理。

# 自定义内容返回
- 首先需要配置
spring.mvc.contentnegotiation.media-types
属性,比如 yaml,这个 yaml 就指的是 url 中的type=yaml
,而值为 Content-Type 传的值。
## 比如yaml类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml
2
//在源码中可以看到media-types是一个Map类型
public void setMediaTypes(Map<String, MediaType> mediaTypes) {
this.mediaTypes = mediaTypes;
}
2
3
4
- 编写对应的
HttpMessageConverter
,要告诉 Boot 这个支持的媒体类型
<!--Jackson已经提供好了yaml格式转换的工具-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
2
3
4
5
/**
* 自定义消息类型转换器
*/
@Configuration
public class MyYamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
private ObjectMapper objectMapper; //jackson提供的
public MyYamlHttpMessageConverter() {
//如果需要的类型没有提供需要自己手写,转换的逻辑
//告诉SpringBoot这个MessageConverter支持哪种媒体类型 //媒体类型
super(new MediaType("text", "yaml", StandardCharsets.UTF_8));
YAMLFactory factory = new YAMLFactory()
//移除yaml中的(“---”)。
.disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
this.objectMapper = new ObjectMapper(factory);
}
@Override
protected boolean supports(Class<?> clazz) {
return !clazz.isPrimitive();
}
//将 HTTP 请求体的内容转换为 Java 对象。在这个方法中,你可以执行多种操作来处理和转换输入数据。
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
// 对输出的内容进行转换
@Override
protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//try-with写法,自动关流
try (OutputStream os = outputMessage.getBody()) {
this.objectMapper.writeValue(os, o);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- 把 MessageConverter 组件加入到容器
@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));
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
WebMvcConfigurer.super.configureMessageConverters(converters);
converters.add(new MyYamlHttpMessageConverter());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 模板引擎
由于 SpringBoot 使用了嵌入式 Servlet 容器。所以 JSP 默认是不能使用的。
如果需要服务端页面渲染,优先考虑使用模板引擎。

模板引擎页面默认放在 src/main/resources/templates
目录下。
SpringBoot 包含以下模板引擎的自动配置
- FreeMarker
- Groovy
- Thymeleaf
- Mustache
Thymeleaf 官网:https://www.thymeleaf.org/
# 整合 Thymeleaf
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2
3
4
自动配置原理
- 开启了 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration 自动配置
- 属性绑定在 ThymeleafProperties 中,对应配置文件 spring.thymeleaf 内容
- 所有的模板页面默认在
classpath:/templates
文件夹下找后缀名为.html
的页面
# 基础语法
# 核心用法
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}" />
2
3
表达式:用来动态取值
${}
:变量取值;使用 model 共享给页面的值都直接用 ${}@{}
:url 路径;#{}
:国际化消息~{}
:片段引用*{}
:变量选择:需要配合 th:object 绑定对象
系统工具 & 内置对象:详细文档 (opens new window)
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}" />
2
3
# 遍历
语法: 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>
2
3
4
5
6
7
8
9
10
11
iterStat 有以下属性:
- index:当前遍历元素的索引,从 0 开始
- count:当前遍历元素的索引,从 1 开始
- size:需要遍历元素的总数量
- current:当前正在遍历的元素对象
- even/odd:是否偶数 / 奇数行
- first:是否第一个元素
- last:是否最后一个元素
# 判断
# th:if
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}"
>view</a
2
3
4
# 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
3
4
5
# 属性优先级
- 片段
- 遍历
- 判断
<ul>
<li th:each="item : ${items}" th:text="${item.description}">Item description here...</li>
</ul>
2
3
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>
2
# 变量选择
<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>
2
3
4
5
等同于
<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
3
4
5
# 模板布局
- 定义模板:
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# devtools
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
2
3
4
修改页面后; 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;
}
2
3
4
5
6
7
8
9
# 错误处理
# 默认机制
错误处理的自动配置都在 ErrorMvcAutoConfiguration
中,两大核心机制:
- SpringBoot 会自适应处理错误,响应页面或 JSON 数据
- SpringMVC 的错误处理机制依然保留,MVC 处理不了,才会交给 boot 进行处理
- 发生错误以后,转发给 /error 路径,SpringBoot 在底层写好一个 BasicErrorController 的组件,专门处理这个请求
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
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());
//1、解析错误的自定义视图地址
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
//2、如果解析不到错误页面的地址,默认的错误页就是 error
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
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);
}
//遍历所有resolveErrorView来处理错误
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
Map<String, Object> model) {
for (ErrorViewResolver resolver : this.errorViewResolvers) {
//遍历调用错误解析器中的解析规则
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 自定义错误视图解析
SpringBoot 提供了一个默认的错误视图解析器,在 ErrorMvcAutoConfiguration
中的内部类 DefaultErrorViewResolverConfiguration
。
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}
2
3
4
5
6
在 DefaultErrorViewResolver
中提供了默认的资源解析规则
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
//首先尝试根据具体的 HTTP 状态码解析视图(如 "404"、"500" 等)。
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
//如果失败,则尝试根据 HTTP 状态码系列解析视图(如 "4xx"、"5xx" 等)。
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) {
//构造错误视图的名称,格式为 "error/[状态码]"
String errorViewName = "error/" + viewName;
//通过templateAvailabilityProviders模板提供者来处理这个视图名称
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
//如果有,返回对应的 ModelAndView。
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
//如果没有,尝试解析为静态资源。
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
//遍历静态资源位置的"状态码.html",位置在{ "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" };
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) {
// Ignore
}
}
return null;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 什么都没有
根据上面所述,没有自定义视图解析,就会去找 /error 页面,但是即使没有 /error 页面也能显示错误的页面信息。
//1、解析错误的自定义视图地址 ModelAndView modelAndView = resolveErrorView(request, response, status, model); //2、如果解析不到错误页面的地址,默认的错误页就是 error return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
1
2
3
4
因为在 ErrorMvcAutoConfiguration
类中内部类 WhitelabelErrorViewConfiguration
中定义了默认的 error 页面显示信息。
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
private static final class StaticView implements View {
private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
private static final Log logger = LogFactory.getLog(StaticView.class);
//这就是默认看到springboot的错误页
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
if (response.isCommitted()) {
String message = getMessage(model);
logger.error(message);
return;
}
response.setContentType(TEXT_HTML_UTF8.toString());
StringBuilder builder = new StringBuilder();
Object timestamp = model.get("timestamp");
Object message = model.get("message");
Object trace = model.get("trace");
if (response.getContentType() == null) {
response.setContentType(getContentType());
}
builder.append("<html><body><h1>Whitelabel Error Page</h1>")
.append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
.append("<div id='created'>")
.append(timestamp)
.append("</div>")
.append("<div>There was an unexpected error (type=")
.append(htmlEscape(model.get("error")))
.append(", status=")
.append(htmlEscape(model.get("status")))
.append(").</div>");
if (message != null) {
builder.append("<div>").append(htmlEscape(message)).append("</div>");
}
if (trace != null) {
builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
}
builder.append("</body></html>");
response.getWriter().append(builder.toString());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 自定义错误响应
# 自定义 json 响应
使用 @ControllerAdvice + @ExceptionHandler 进行统一异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
// 自定义异常类型
@ExceptionHandler(CustomException.class)
public ResponseEntity<ApiResponse> handleCustomException(CustomException ex) {
ApiResponse apiResponse = new ApiResponse("error", ex.getMessage(), null);
return new ResponseEntity<>(apiResponse, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse> handleGenericException(Exception ex) {
ApiResponse apiResponse = new ApiResponse("error", "An unexpected error occurred", null);
return new ResponseEntity<>(apiResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
// 自定义异常
public class CustomException extends RuntimeException {
public CustomException(String message) {
super(message);
}
}
@Data
@AllArgsConstructor
// API 响应类
public class ApiResponse {
private String status;
private String message;
private Object data;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 自定义页面响应
上面已经写过了如何去自定义页面响应。
# 最佳实战
# 前后分离
后台发生的所有错误, @ControllerAdvice + @ExceptionHandler
进行统一异常处理。
# 服务端页面渲染
不可预知的一些,HTTP 码表示的服务器或客户端错误
- 给
classpath:/templates/error/
下面,放常用精确的错误码页面。500.html
,404.html
- 给
classpath:/templates/error/
下面,放通用模糊匹配的错误码页面。5xx.html
,4xx.html
发生业务错误
- 核心业务,每一种错误,都应该代码控制,跳转到自己定制的错误页。
- 通用业务,
classpath:/templates/error.html
页面,显示错误信息。
页面,JSON,可用的 Model 数据如下

# 嵌入式容器
Servlet 容器:管理、运行 Servlet 组件(Servlet、Filter、Listener)的环境,一般指服务器
# 自动配置原理
- SpringBoot 默认嵌入 Tomcat 作为 Servlet 容器。
- 自动配置类是
ServletWebServerFactoryAutoConfiguration
,EmbeddedWebServerFactoryCustomizerAutoConfiguration
- 自动配置类开始分析功能。
xxxxAutoConfiguration
@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
//导入了嵌入式的三大服务器 `Tomcat`、`Jetty`、`Undertow`
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {}
2
3
4
5
6
7
8
9
10
11
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);
}
}
2
3
4
5
6
7
8
9
10
Web 场景的 Spring 容器启动,在 onRefresh 的时候,会调用创建 web 服务器的方法。
Web 服务器的创建是通过 WebServerFactory 搞定的。容器中又会根据导了什么包条件注解,启动相关的 服务器配置,默认 EmbeddedTomcat
会给容器中放一个 TomcatServletWebServerFactory
,导致项目启动,自动创建出 Tomcat。
# 切换嵌入式容器
由于 web 模块默认导入了 Tomcat 服务器,所以需要把 Tomcat 从 web 模块中排除,然后再导入其他的服务器
<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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Web 新特性
# Problemdetails
RFC 7807 (opens new window): 错误信息新的返回格式
在源码中
@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();
}
}
2
3
4
5
6
7
8
9
10
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
默认的返回是
{
"timestamp": "2024-07-16T09:05:48.689+00:00",
"status": 405,
"error": "Method Not Allowed",
"path": "/person"
}
2
3
4
5
6
开启了 spring.mvc.problemdetails.enabled=true
{
"type": "about:blank",
"title": "Method Not Allowed",
"status": 405,
"detail": "Method 'POST' is not supported.",
"instance": "/person"
}
2
3
4
5
6
7
# 函数式 Web
SpringMVC 5.2
以后 允许我们使用函数式的方式,定义 Web 的请求处理流程。
函数式接口
Web 请求处理的方式:
@Controller + @RequestMapping
:耦合式 (路由、业务耦合)- 函数式 Web:分离式(路由、业务分离)
# 场景
场景:User RESTful - CRUD
- GET /user/1 获取 1 号用户
- GET /users 获取所有用户
- POST /user 请求体携带 JSON,新增一个用户
- PUT /user/1 请求体携带 JSON,修改 1 号用户
- DELETE /user/1 删除 1 号用户
# 核心类
- RouterFunction:定义路由信息。发什么请求,谁来处理。
- RequestPredicate:请求谓语也指请求方式如 GET、POST、请求参数。
- ServerRequest:封装完整请求数据
- ServerResponse:封装完整的响应数据
# 示例
import static org.springframework.web.servlet.function.RequestPredicates.accept;
import static org.springframework.web.servlet.function.RouterFunctions.route;
/**
* Spring Web 新特性函数式Web,路由和业务分开处理,这里配置路由
*/
@Configuration
public class MyRoutingConfig {
private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
@Bean
public RouterFunction<ServerResponse> routerFunction(UserHandler userHandler) {
return route()
.GET("/user/{id}", ACCEPT_JSON, userHandler::getUser)
.POST("user/add",ACCEPT_JSON,userHandler::addUser)
.build();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 用户模块实际的业务处理逻辑
*/
@Component
@Slf4j
public class UserHandler {
//模拟查询用户方法
public ServerResponse getUser(ServerRequest request) {
String userId = request.pathVariable("id");
// 使用userId进行后续处理,例如从数据库获取用户
User user = new User(Long.parseLong(userId), "东东", 20);
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(user);
}
//模拟添加用户方法
public ServerResponse addUser(ServerRequest request) {
try {
User user = request.body(User.class);
log.info("user:{}", user);
} catch (Exception e) {
e.printStackTrace();
}
return ServerResponse.ok().body(ResponseEntity.ok("OK"));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27