1. 概述
本教程将深入探讨 Spring 5 引入的 ResponseStatusException
类。这个类的主要作用是为 HTTP 响应设置状态码。
在 RESTful 应用中,通过在响应中返回正确的 HTTP 状态码,可以清晰地向客户端传达请求处理的成功或失败。简单来说,合适的 HTTP 状态码能帮助客户端快速定位请求处理过程中可能出现的问题。
2. @ResponseStatus
注解
在深入 ResponseStatusException
之前,我们先快速回顾一下 @ResponseStatus
注解。这个注解自 Spring 3 起就用于为 HTTP 响应设置状态码。
我们可以用 @ResponseStatus
注解来设置响应的状态码和原因:
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Actor Not Found")
public class ActorNotFoundException extends Exception {
// ...
}
当处理 HTTP 请求时抛出此异常,响应会自动包含注解中指定的 HTTP 状态码。
这种方式的缺点是会造成异常与状态码的强耦合。例如,所有 ActorNotFoundException
异常都会在响应中生成相同的错误消息和状态码。
3. ResponseStatusException
ResponseStatusException
是 @ResponseStatus
的编程式替代方案,也是用于设置 HTTP 响应状态码的异常基类。它是一个 RuntimeException
,因此不需要在方法签名中显式声明。
Spring 提供了三个构造函数来创建 ResponseStatusException
:
ResponseStatusException(HttpStatus status)
ResponseStatusException(HttpStatus status, java.lang.String reason)
ResponseStatusException(
HttpStatus status,
java.lang.String reason,
java.lang.Throwable cause
)
构造参数说明:
status
- 设置到 HTTP 响应中的状态码reason
- 解释异常原因的消息(会包含在 HTTP 响应中)cause
- 导致ResponseStatusException
的原始异常
⚠️ 注意:Spring 中的 HandlerExceptionResolver
会拦截并处理所有未被 Controller 捕获的异常。其中 ResponseStatusExceptionResolver
会专门处理 ResponseStatusException
或带有 @ResponseStatus
注解的未捕获异常,从中提取状态码和原因信息并添加到 HTTP 响应中。
3.1. ResponseStatusException
的优势
使用 ResponseStatusException
有几个明显优势:
- ✅ 解耦性:同一类型的异常可以分别处理,设置不同的响应状态码,避免强耦合
- ✅ 减少类数量:无需创建大量自定义异常类
- ✅ 灵活控制:支持编程式创建异常,提供更精细的异常处理控制
4. 实战示例
4.1. 生成 ResponseStatusException
下面是一个生成 ResponseStatusException
的示例:
@GetMapping("/actor/{id}")
public String getActorName(@PathVariable("id") int id) {
try {
return actorService.getActor(id);
} catch (ActorNotFoundException ex) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Actor Not Found", ex);
}
}
Spring Boot 默认提供 /error
映射,返回包含 HTTP 状态码的 JSON 响应:
$ curl -i -s -X GET 'http://localhost:8081/actor/8'
HTTP/1.1 404
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 26 Dec 2020 19:38:09 GMT
{
"timestamp": "2020-12-26T19:38:09.426+00:00",
"status": 404,
"error": "Not Found",
"path": "/actor/8"
}
细心的读者可能发现,响应中缺少了显示原因文本 "Actor Not Found" 的 message
字段。接下来我们分析原因并解决它。
4.2. 关于 server.error.include-message
属性
server.error.include-message
属性控制错误响应中是否包含 message
字段。它支持三个值:
always
- 错误响应始终包含message
字段never
- 错误响应从不包含message
字段on_param
- 仅当请求包含message=true
参数时才包含message
字段
⚠️ **从 Spring Boot 2.3 开始,该属性默认值为 never
**。这是为了减少向客户端泄露敏感信息的风险。因此前面的示例响应中没有 message
字段。
现在设置 server.error.include-message=on_param
并测试:
$ curl -i -s -X GET 'http://localhost:8081/actor/8'
HTTP/1.1 404
...
{
"timestamp": "2020-12-26T19:38:49.426+00:00",
"status": 404,
"error": "Not Found",
"path": "/actor/8"
}
当请求不带 message=true
参数时,响应不包含 message
。添加参数后:
$ curl -i -s -X GET 'http://localhost:8081/actor/8?message=true'
HTTP/1.1 404
...
{
"timestamp": "2020-12-26T19:49:11.426+00:00",
"status": 404,
"error": "Not Found",
"message": "Actor Not Found",
"path": "/actor/8"
}
💡 开发提示:Spring Boot DevTools 会将 server.error.include-message
默认设为 always
。开发时启用 DevTools 可以方便地查看完整错误信息。
为简化演示,**本教程后续示例将设置 server.error.include-message=always
**。
4.3. 同一异常类型返回不同状态码
现在演示如何对同一异常类型返回不同的 HTTP 状态码:
@PutMapping("/actor/{id}/{name}")
public String updateActorName(
@PathVariable("id") int id,
@PathVariable("name") String name) {
try {
return actorService.updateActor(id, name);
} catch (ActorNotFoundException ex) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "Provide correct Actor Id", ex);
}
}
响应示例:
$ curl -i -s -X PUT 'http://localhost:8081/actor/8/BradPitt'
HTTP/1.1 400
...
{
"timestamp": "2018-02-01T04:28:32.917+0000",
"status": 400,
"error": "Bad Request",
"message": "Provide correct Actor Id",
"path": "/actor/8/BradPitt"
}
5. 总结
本教程深入探讨了 ResponseStatusException
的使用方法。相比传统的 @ResponseStatus
注解,它提供了更灵活的编程式 HTTP 状态码设置方式,特别适合需要动态控制响应状态的场景。
完整源代码可在 GitHub 获取。