You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 20:12:20