Spring Webflux Netty服务缺失路径参数返回400而非404问题排查
问题描述
我使用Spring Boot 2.6.6 + Spring Webflux 5.3.18,基于Netty搭建响应式REST服务。测试接口{{baseUrl}}/person/{id}时,当路径参数id为空/缺失(请求地址为{{baseUrl}}/person/),期望返回404状态码,但实际返回400,且代码未进入自定义的GlobalExceptionHandler。请问是否需要通过Filter添加额外逻辑处理该400并传递到全局异常处理器?该怎么解决?
接口定义:
Mono<ResponseEntity<Person>> getPerson( @Parameter(name = "id", description = "The Person identifier.", required = true) @PathVariable("id") String id, @Parameter(hidden = true) final ServerWebExchange exchange );
全局异常处理器:
@Slf4j @Component public class GlobalExceptionHandler implements ErrorWebExceptionHandler { @Override public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { if (ex instanceof WebClientResponseException && ((WebClientResponseException)ex).getStatusCode() == HttpStatus.NOT_FOUND){ exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND); return writeErrorResponse(exchange, "NOT_FOUND"); } exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); return writeErrorResponse(exchange, "INTERNAL_SERVER_ERROR"); } private Mono<Void> writeErrorResponse(ServerWebExchange exchange,String errorMessage) { DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory(); return exchange.getResponse().writeWith(Mono.just(bufferFactory.wrap(errorMessage.getBytes()))); } }
解决方案
不需要额外添加Filter,问题核心在于路由匹配阶段抛出的异常类型未被你的全局处理器覆盖,以及异常场景的判断逻辑缺失。
1. 为什么400未进入全局异常处理器?
当请求{{baseUrl}}/person/时,Spring Webflux的路由匹配环节会先校验路径参数,此时抛出的是ServerWebInputException(属于ResponseStatusException子类,对应400状态码),但你的全局处理器仅处理WebClientResponseException,因此不会触发自定义逻辑。
2. 修改全局异常处理器适配场景
更新GlobalExceptionHandler,增加对ServerWebInputException的判断,并针对路径参数缺失的场景返回404:
@Slf4j @Component public class GlobalExceptionHandler implements ErrorWebExceptionHandler { @Override public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { // 处理路径参数缺失导致的400,转为404 if (ex instanceof ServerWebInputException) { ServerWebInputException inputEx = (ServerWebInputException) ex; // 通用判断:匹配异常信息中路径参数缺失的特征 if (inputEx.getMessage().contains("Required path variable 'id' is not present")) { exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND); return writeErrorResponse(exchange, "NOT_FOUND"); } // 其他参数错误仍返回400 exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST); return writeErrorResponse(exchange, "BAD_REQUEST"); } // 原有WebClientResponseException的处理逻辑 if (ex instanceof WebClientResponseException && ((WebClientResponseException)ex).getStatusCode() == HttpStatus.NOT_FOUND){ exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND); return writeErrorResponse(exchange, "NOT_FOUND"); } // 默认返回500 exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); return writeErrorResponse(exchange, "INTERNAL_SERVER_ERROR"); } private Mono<Void> writeErrorResponse(ServerWebExchange exchange,String errorMessage) { DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory(); return exchange.getResponse().writeWith(Mono.just(bufferFactory.wrap(errorMessage.getBytes()))); } }
3. 路由配置层面的优化
确保你的路由配置中没有匹配/person/的冗余路由,统一路由格式:
- 正确的带参数路由:
/person/{id} - 避免配置
/person/这类无参数路由,防止路由匹配混淆
内容的提问来源于stack exchange,提问作者jcrshankar
相关产品推荐
相关产品推荐

