1. 概述
随着 Spring Boot 2 和基于响应式编程的非阻塞服务器 Netty 的引入,Servlet 上下文 API 已经不复存在。因此,在新的响应式栈中,我们需要重新审视如何正确地返回各种 HTTP 状态码。
2. 语义化的响应状态码
遵循 RESTful 最佳实践,我们自然需要充分利用 HTTP 状态码来表达 API 的语义。
2.1. 默认返回状态码
当一切顺利时,默认返回的状态码是 **200 (OK)**:
@GetMapping(
value = "/ok",
produces = MediaType.APPLICATION_JSON_UTF8_VALUE
)
public Flux<String> ok() {
return Flux.just("ok");
}
2.2. 使用注解修改状态码
可以通过在方法上添加 @ResponseStatus
注解来改变默认返回的状态码:
@GetMapping(
value = "/no-content",
produces = MediaType.APPLICATION_JSON_UTF8_VALUE
)
@ResponseStatus(HttpStatus.NO_CONTENT)
public Flux<String> noContent() {
return Flux.empty();
}
2.3. 程序化修改状态码
有些场景下,我们需要根据运行时逻辑动态决定返回的状态码,而不是使用固定的注解或默认值。这时候可以注入 ServerHttpResponse
来手动设置状态码:
@GetMapping(
value = "/accepted",
produces = MediaType.APPLICATION_JSON_UTF8_VALUE
)
public Flux<String> accepted(ServerHttpResponse response) {
response.setStatusCode(HttpStatus.ACCEPTED);
return Flux.just("accepted");
}
这种方式更灵活,适合复杂业务逻辑下的状态码控制。
2.4. 抛出异常返回状态码
一旦抛出异常,默认的 HTTP 状态码将被忽略,Spring 会尝试寻找对应的异常处理器来处理它:
@GetMapping(
value = "/bad-request"
)
public Mono<String> badRequest() {
return Mono.error(new IllegalArgumentException());
}
@ResponseStatus(
value = HttpStatus.BAD_REQUEST,
reason = "Illegal arguments")
@ExceptionHandler(IllegalArgumentException.class)
public void illegalArgumentHandler() {
//
}
更多关于异常处理的内容,可以参考 Baeldung 的 Spring REST 异常处理指南。
2.5. 使用 ResponseEntity
ResponseEntity
是一个非常灵活的选择,它不仅允许我们自定义状态码,还可以添加响应头等额外信息:
@GetMapping(
value = "/unauthorized"
)
public ResponseEntity<Mono<String>> unathorized() {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.header("X-Reason", "user-invalid")
.body(Mono.just("unauthorized"));
}
✅ 适合需要精细控制响应结构的场景
⚠️ 注意不要滥用,避免代码冗余
2.6. 函数式接口设置状态码
Spring 5 支持使用函数式方式定义接口(RouterFunction),同样可以灵活地控制状态码:
@Bean
public RouterFunction<ServerResponse> notFound() {
return RouterFunctions
.route(GET("/statuses/not-found"),
request -> ServerResponse.notFound().build());
}
这种方式非常适合构建轻量、高性能的响应式接口。
3. 总结
在构建 HTTP API 时,框架提供了多种方式来优雅地处理返回的状态码。本文从不同角度介绍了如何在 Spring WebFlux 中灵活返回 404 或其他状态码,帮助你构建语义清晰、符合 RESTful 风格的接口。
踩坑提醒:不要忽略状态码的语义,乱用 200 返回所有情况,那样会让前端或调用方非常头大 😵。