5. 模板引擎
- 由于 SpringBoot 使用了嵌入式 Servlet 容器 (tomca)。所以 JSP 默认是不能使用的。
- 如果需要服务端页面渲染,优先考虑使用 模板引擎。
模板引擎页面默认放在 src/main/resources/templates
SpringBoot 包含以下模板引擎的自动配置
- FreeMarker
- Groovy
- Thymeleaf
- Mustache
Thymeleaf官网:https://www.thymeleaf.org/
<!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>
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
的页面
- 所有的模板页面在
使用举例:
1.html文件:
2.controller:
注意,这里只能写controller,thymeleaf是类似于前后端不分离模式:
5.2. 基础语法:
在html文件中html标签中加上下列代码就有thymeleaf代码提示。
<html xmlns:th="http://www.thymeleaf.org">
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)
特殊语法:
- 无操作:_
所有以上都可以嵌套组合
'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
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}" />
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:是否最后一个元素
注意,对于下面的代码:
<td th:text="${prod.name}">Onions</td>
我们也可以写成:
<td> [[${prod.name}]] </td>
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>
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 |
5.7. 行内写法
[[...]] or [(...)]
<p>Hello, [[${session.user.name}]]!</p>
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
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>
在运用中,我们一般把公共页面要运用的东西单独写在一个类里面,并制定他的名字(比如这段代码的首行),这样,在其他html文件需要用到这些公共的部分,比如导航栏,就可以直接引入模板:
<div th:insert="~{footer :: copy}"></div>
copy是名字,footer是thymeleaf路径下的footer.html文件。
5.10. devtools
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
动态改变,热编译。
修改页面后;ctrl+F9
刷新效果;
注意,java代码的修改,如果devtools
热启动了,可能会引起一些bug,难以排查。
5.11.其他配置:
cache是缓存相关的,开发时为了不清缓存就看到效果,记得关闭,当然,上线时记得开启。
6. 国际化
国际化的自动配置参照MessageSourceAutoConfiguration
注意,一定要确保编码为UTF-8。
实现步骤:
-
Spring Boot 在类路径根下查找messages资源绑定文件。文件名为:messages.properties
-
多语言可以定义多个消息文件,命名为
messages_区域代码.properties
。如: -
messages.properties
:默认messages_zh_CN.properties
:中文环境messages_en_US.properties
:英语环境
-
在程序中可以自动注入
MessageSource
组件,获取国际化的配置项值 -
在页面中可以使用表达式
#{}
获取国际化的配置项值,{}填写配置文件中的key。即下面的login。
login=Login
login=登录
<h1 th:text="#{login}"></h1>
国际化配置:
@Autowired //国际化取消息用的组件
MessageSource messageSource;
@GetMapping("/haha")
public String haha(HttpServletRequest request){
//获取区域信息
Locale locale = request.getLocale();
//利用代码的方式获取国际化配置文件中指定的配置项的值
String login = messageSource.getMessage("login", null, locale);
return login;
}
7. 错误处理
7.1.默认机制
错误处理的自动配置都在ErrorMvcAutoConfiguration中,两大核心机制:
- SpringBoot 会自适应处理错误,响应页面或JSON数据
- SpringMVC的错误处理机制依然保留,MVC处理不了,才会交给boot进行处理
以前常用的自定义全局错误处理:
注意,标明@ControllerAdvice就是全局处理,如果写在一个类的内部,即只是处理该类的错误。
如果还是不能处理错误,那么就转发给/error路径:
默认配置如下:
●发生错误以后,转发给/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);
}
●错误页面是这么解析到的
//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);
}
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;
}
解析如下:
resolveErrorView
方法
这个方法尝试解析一个与HTTP状态码对应的错误视图。
- 参数:
HttpServletRequest request
:当前的HTTP请求。HttpStatus status
:HTTP状态码。Map<String, Object> model
:模型数据,用于渲染视图。
- 方法内容:
- 首先,它尝试解析一个与具体状态码值对应的视图(例如
404
、500
等)。 - 如果没有找到与具体状态码值对应的视图,它会检查状态码所属的系列(如
4xx
、5xx
),并尝试解析一个与系列对应的通用视图。 - 如果两者都失败,则返回
null
。
- 首先,它尝试解析一个与具体状态码值对应的视图(例如
resolve
方法
这个方法尝试解析一个特定的视图名称。
- 参数:
viewName
:视图名称。model
:模型数据。
- 方法内容:
- 构造一个具体的视图名称(如
error/404
)。 - 使用
TemplateAvailabilityProvider
检查该视图是否可用。 - 如果可用,返回一个新的
ModelAndView
对象。 - 如果不可用,调用
resolveResource
方法尝试从资源位置解析视图。
- 构造一个具体的视图名称(如
resolveResource
方法
这个方法尝试从静态资源位置解析一个视图。
-
参数:
viewName
:视图名称。model
:模型数据。
-
方法内容:
- 遍历所有静态资源位置。
- 对于每个位置,尝试构造一个对应的资源路径(如
static/error/404.html
)。 - 如果资源存在,返回一个新的
ModelAndView
对象,该对象使用HtmlResourceView
来渲染HTML资源。 - 如果在遍历过程中发生异常,则捕获异常并继续尝试下一个位置。
- 如果所有位置都没有找到对应的资源,返回
null
。
容器中有一个默认的名为 error 的 view; 提供了默认白页功能
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
封装了JSON格式的错误信息
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
规则:
解析一个错误页:
7.2. 自定义错误响应
1. 自定义json响应
使用@ControllerAdvice + @ExceptionHandler 进行统一异常处理
2. 自定义页面响应
根据boot的错误页面规则,自定义页面模板
7.3. 最佳实战
页面,JSON,可用的Model数据如下
在响应的页面中,可以用插值表达式返回错误的trace。
8. 嵌入式容器
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)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
}
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>
<!-- 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>
9. 全面接管SpringMVC
-
SpringBoot 默认配置好了 SpringMVC 的所有常用特性。
-
如果我们需要全面接管SpringMVC的所有配置并禁用默认配置,仅需要编写一个
WebMvcConfigurer
配置类,并标注@EnableWebMvc
即可 -
全手动模式
-
@EnableWebMvc
: 禁用默认配置WebMvcConfigurer
组件:定义MVC的底层行为
9.1. WebMvcAutoConfiguration 到底自动配置了哪些规则
SpringMVC自动配置场景给我们配置了如下所有默认行为
-
WebMvcAutoConfiguration
web场景的自动配置类 -
-
支持RESTful的filter:HiddenHttpMethodFilter
-
支持非POST请求,请求体携带数据:FormContentFilter
-
导入
EnableWebMvcConfiguration
:
-
-
-
RequestMappingHandlerAdapter
WelcomePageHandlerMapping
: 欢迎页功能支持(模板引擎目录、静态资源目录放index.html),项目访问/ 就默认展示这个页面.RequestMappingHandlerMapping
:找每个请求由谁处理的映射关系ExceptionHandlerExceptionResolver
:默认的异常解析器LocaleResolver
:国际化解析器ThemeResolver
:主题解析器FlashMapManager
:临时数据共享FormattingConversionService
: 数据格式化 、类型转化,允许我们自定义格式。Validator
: 数据校验JSR303
提供的数据校验功能WebBindingInitializer
:请求参数的封装与绑定ContentNegotiationManager
:内容协商管理器
-
-
4.
WebMvcAutoConfigurationAdapter
配置生效,它是一个WebMvcConfigurer
,定义mvc底层组件 -
-
- 定义好
WebMvcConfigurer
底层组件默认功能;所有功能详见列表 - 视图解析器:
InternalResourceViewResolver
- 视图解析器:
BeanNameViewResolver
,**视图名(controller方法的返回值字符串)**就是组件名 - 内容协商解析器:
ContentNegotiatingViewResolver
- 请求上下文过滤器:
RequestContextFilter
: 任意位置直接获取当前请求 - 静态资源链规则
ProblemDetailsExceptionHandler
:错误详情
- 定义好
-
-
-
-
- SpringMVC内部场景异常被它捕获:
-
-
-
5.定义了MVC默认的底层行为:
WebMvcConfigurer
9.2. 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 | 参数解析器: | mvc 默认提供 |
addReturnValueHandlers | List | 返回值解析器: | mvc 默认提供 |
configureHandlerExceptionResolvers | List | 异常处理器: | 默认 3 个 ExceptionHandlerExceptionResolver ResponseStatusExceptionResolver DefaultHandlerExceptionResolver |
getMessageCodesResolver | 无 | 消息码解析器:国际化使用 | 无 |
9.3. @EnableWebMvc 禁用默认行为
@EnableWebMvc
给容器中导入DelegatingWebMvcConfiguration
组件,
他是 WebMvcConfigurationSupport
WebMvcAutoConfiguration
有一个核心的条件注解,@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
,容器中没有WebMvcConfigurationSupport
,WebMvcAutoConfiguration
才生效.- @EnableWebMvc 导入
WebMvcConfigurationSupport
导致WebMvcAutoConfiguration
失效。导致禁用了默认行为
- @EnableWebMVC 禁用了 Mvc的自动配置
- WebMvcConfigurer 定义SpringMVC底层组件的功能类
两种模式
1、前后分离模式
: @RestController
响应JSON数据
2、前后不分离模式
:@Controller + Thymeleaf模板引擎
11. Web新特性
11.1. Problemdetails
RFC 7807: https://www.rfc-editor.org/rfc/rfc7807
错误信息返回新格式
注意,不是默认开启。
spring.mvc.problemdetails.enabled=true
可以加上这句话。
@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
})
效果:
默认响应错误的json。状态码 405:
{
"timestamp": "2023-04-18T11:13:05.515+00:00",
"status": 405,
"error": "Method Not Allowed",
"trace": "org.springframework.web.HttpRequestMethodNotSupportedException",
"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"
}
11.2. 函数式Web
SpringMVC 5.2
以后 允许我们使用函数式的方式,定义Web的请求处理流程。
函数式接口
Web请求处理的方式:
@Controller + @RequestMapping
:耦合式 (路由、业务耦合)- 函数式Web:分离式(路由、业务分离)
2.1. 场景
场景:User RESTful - CRUD
- GET /user/1 获取1号用户
- GET /users 获取所有用户
- POST /user 请求体携带JSON,新增一个用户
- PUT /user/1 请求体携带JSON,修改1号用户
- DELETE /user/1 删除1号用户
2.2. 核心类
2.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();
}
}
配置路由的格式举例:
GET("/{user}", ACCEPT_JSON, userHandler::getUser)
-
"/{user}"
- 路径模板这个参数定义了请求的URL路径模式。在这里,
/{user}
是一个路径模板,其中{user}
是一个占位符。当实际的HTTP请求到达时,服务器会尝试将请求的URL与这个模板进行匹配。如果匹配成功,{user}
将被替换为请求URL中对应部分的实际值。例如,如果请求的URL是/john
,那么{user}
的值就是john
。路径模板允许你定义具有可变部分的URL,这使得你的应用程序能够处理多个不同的用户请求,而不需要为每个用户都定义一个单独的路由。
-
ACCEPT_JSON
- 请求条件这个参数通常用于指定处理该路由时所需的请求条件。在这里,
ACCEPT_JSON
可能是一个常量或配置,用于指示该路由只处理那些请求头中包含Accept: application/json
的请求。这意味着客户端期望服务器返回的响应体是JSON格式的。通过指定请求条件,你可以确保你的应用程序只处理那些符合特定要求的请求,这有助于保持代码的清晰性和可维护性。
-
userHandler::getUser
- 处理函数这个参数是一个函数指针或可调用对象,它定义了当请求的URL与路径模板匹配并且满足请求条件时应该调用的函数或方法。在这个例子中,
userHandler::getUser
是一个成员函数指针,它指向userHandler
类中定义的getUser
方法。当路由被触发时,
getUser
方法会被调用,并且通常会传入一些参数,比如请求对象、响应对象以及从URL路径中提取的任何动态部分(如{user}
的值)。然后,getUser
方法会执行相应的逻辑来处理请求,并可能生成一个响应返回给客户端。我们一般在其他的类中集中写出处理函数,然后将该类传参传入。举例:
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();
}
}