Spring Boot使用@ControllerAdvice自定义404异常响应失效问题
问题核心原因
你之前的两套方案不生效,本质是踩了Spring MVC请求处理流程的坑:
- 拦截器的
preHandle方法执行时,Spring还没完成请求路径和Controller的匹配,响应状态默认是200,根本不可能提前判断出404;等afterCompletion执行时,响应内容已经提交给客户端了,这时候抛异常只会打日志,根本不会被全局异常处理器捕获,只会返回容器默认的HTML错误页。 - 只配置
spring.mvc.throw-exception-if-no-handler-found=true不够,Spring Boot默认的错误页自动配置会提前拦截NoHandlerFoundException,返回默认的错误JSON/HTML,根本轮不到你写的@RestControllerAdvice处理。
实际测试时抛出的异常不会被全局处理器接收,最终返回HTML错误页:
可直接落地的实现方案
第一步:修正配置文件
把application.properties里的相关配置替换成下面的内容,从配置层面放开404异常的抛出,关掉默认错误页的拦截:
# 路径无匹配Handler时直接抛出NoHandlerFoundException spring.mvc.throw-exception-if-no-handler-found=true # 关闭静态资源默认映射,避免静态资源Handler兜底所有请求导致异常不抛出 spring.web.resources.add-mappings=false # 排除Spring Boot默认的错误页自动配置,避免它提前拦截异常 spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
第二步:删掉拦截器里无效的404处理逻辑
拦截器的生命周期决定了它不适合处理404场景,把preHandle和afterCompletion里判断404、读请求体、抛异常的代码全删掉,不要在这层做错误处理。
第三步:修正全局异常处理器
直接在@RestControllerAdvice里捕获NoHandlerFoundException即可,注意404场景要返回正确的HTTP状态码,不要所有异常都返回500:
@RestControllerAdvice public class GlobalExceptionHandler { private final ApplicationExchangeContext context; private final ErrorMessageBuilder errorMessageBuilder; // 构造注入依赖,不需要在拦截器里手动往上下文塞请求参数 public GlobalExceptionHandler(ApplicationExchangeContext context, ErrorMessageBuilder errorMessageBuilder) { this.context = context; this.errorMessageBuilder = errorMessageBuilder; } // 专门处理404异常 @ExceptionHandler(NoHandlerFoundException.class) public ResponseEntity<ErrorMessage> handleNoHandlerFound(NoHandlerFoundException ex, WebRequest request) { FacturxDto requestDto = context.getFacturxDtoContext(); ErrorMessage resp = errorMessageBuilder(requestDto, ex); // 注意返回404状态码,不是500 return ResponseEntity.status(HttpStatus.NOT_FOUND).body(resp); } // 保留你原来的业务异常处理逻辑 @ExceptionHandler(value = {AppServiceException.class, ServletServiceException.class }) public ResponseEntity<Object> handleAppServiceException(Exception ex, WebRequest req) { FacturxDto request = context.getFacturxDtoContext(); ErrorMessage errorMessage = errorMessageBuilder(request, ex); return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR); } }
兜底方案(全版本兼容100%生效)
如果上面的配置因为Spring Boot版本差异还是不生效,直接替换默认的错误属性实现即可,这是Spring Boot错误处理流程的最后一环,所有没被上层捕获的异常(包括404)都会走这里:
@Component public class CustomErrorAttributes extends DefaultErrorAttributes { private final ApplicationExchangeContext context; public CustomErrorAttributes(ApplicationExchangeContext context) { this.context = context; } @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { Integer statusCode = (Integer) webRequest.getAttribute( RequestDispatcher.ERROR_STATUS_CODE, WebRequest.SCOPE_REQUEST ); Throwable ex = getError(webRequest); FacturxDto request = context.getFacturxDtoContext(); // 复用你原来的错误响应构建逻辑 ErrorMessage errorMessage = errorMessageBuilder(request, ex); // 把自定义响应对象转成Map返回即可 return new ObjectMapper().convertValue(errorMessage, new TypeReference<>() {}); } }
注意:不要在拦截器的
afterCompletion阶段写响应、抛异常,这个阶段响应已经输出到客户端了,所有修改操作都不会生效。
内容的提问来源于stack exchange,提问作者davidvera
相关产品推荐
相关产品推荐

