Spring MVC异常处理利器:深入理解HandlerExceptionResolver

引言

在 Web 开发中,异常处理是保障系统健壮性和用户体验的关键环节。Spring MVC 提供了多种异常处理机制,其中 HandlerExceptionResolver 是一个强大且灵活的底层工具。本文将深入探讨它的工作原理、使用场景、实战技巧,并通过具体代码示例展示如何全局统一处理异常。

什么是 HandlerExceptionResolver

HandlerExceptionResolver 是 Spring MVC 中用于统一处理控制器方法执行过程中抛出异常的接口。它允许开发者将异常转换为特定的 HTTP 响应,例如:

  • 跳转到自定义错误页面(HTML)

  • 返回结构化的错误 JSON(API 接口)

  • 设置 HTTP 状态码(如 404、500)

通过实现该接口,可以集中管理所有异常,避免在每个控制器中重复编写 try-catch 代码。

核心方法与处理流程:

java 复制代码
ModelAndView resolveException(
    HttpServletRequest request,
    HttpServletResponse response,
    Object handler,
    Exception ex
);
  • 参数

    • requestresponse:操作 HTTP 请求和响应。

    • handler:触发异常的控制器方法。

    • ex:抛出的异常对象。

  • 返回值

    • ModelAndView:封装视图和数据,若返回 null 则表示未处理异常,由其他解析器继续处理。

处理流程

  1. 控制器方法抛出异常。

  2. Spring MVC 遍历所有注册的 HandlerExceptionResolver

  3. 第一个能处理该异常的解析器(返回非 null)终止流程。

  4. 若所有解析器均未处理,则交由容器默认处理(如 Tomcat 的 Whitelabel 错误页)

自定义 HandlerExceptionResolver 实战

场景需求

假设项目中需实现以下异常处理逻辑:

  1. Web 请求:跳转到美观的错误页面,并显示友好提示。

  2. API 请求:返回 JSON 格式的错误信息,包含错误码和消息。

  3. 特定异常 :如 BusinessException 需记录日志并返回自定义 HTTP 状态码。

代码实现

1. 创建自定义解析器

java 复制代码
public class GlobalExceptionResolver implements HandlerExceptionResolver, Ordered {

    @Override
    public ModelAndView resolveException(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler,
            Exception ex) {

        // 判断请求类型:API 或 Web
        boolean isApiRequest = request.getRequestURI().startsWith("/api/") 
                || "application/json".equals(request.getHeader("Accept"));

        if (isApiRequest) {
            return handleApiException(response, ex);
        } else {
            return handleWebException(ex);
        }
    }

    private ModelAndView handleApiException(HttpServletResponse response, Exception ex) {
        try {
            response.setContentType("application/json");
            response.setStatus(500); // 默认状态码

            // 构建 JSON 响应体
            Map<String, Object> error = new HashMap<>();
            error.put("code", 500);
            error.put("message", "服务异常,请稍后重试");
            
            // 针对特定异常细化处理
            if (ex instanceof BusinessException) {
                BusinessException bex = (BusinessException) ex;
                error.put("code", bex.getCode());
                error.put("message", bex.getMessage());
                response.setStatus(bex.getHttpStatus().value());
            }

            // 写入响应流
            response.getWriter().write(new ObjectMapper().writeValueAsString(error));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView(); // 返回空表示已处理
    }

    private ModelAndView handleWebException(Exception ex) {
        ModelAndView mav = new ModelAndView("error-page"); // 模板路径
        mav.addObject("errorMsg", ex.getMessage());

        // 记录日志
        log.error("Web请求异常: ", ex);
        return mav;
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE; // 最高优先级
    }
}

2. 注册解析器

java 复制代码
@Configuration
public class WebMvcConfig {

    @Bean
    public HandlerExceptionResolver globalExceptionResolver() {
        return new GlobalExceptionResolver();
    }
}

3. HTML 错误页模板(Thymeleaf)

resources/templates/error-page.html

java 复制代码
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>错误提示</title>
</head>
<body>
    <h1>抱歉,系统开小差了!</h1>
    <p th:text="${errorMsg}"></p>
    <a href="/">返回首页</a>
</body>
</html>

高级技巧与最佳实践

1. 优先级控制

通过实现 Ordered 接口或使用 @Order 注解,确保自定义解析器优先于 Spring 默认解析器执行。例如:

java 复制代码
@Override
public int getOrder() {
    return Ordered.HIGHEST_PRECEDENCE; // 值越小,优先级越高
}

2. 异常日志记录

在解析器中统一记录异常日志,便于问题排查:

java 复制代码
@Override
public ModelAndView resolveException(...) {
    log.error("请求路径: {}, 异常信息: {}", request.getRequestURI(), ex.getMessage(), ex);
    // ...处理逻辑
}

3. 区分异常类型

针对不同异常定制处理逻辑,提升用户体验:

java 复制代码
if (ex instanceof ResourceNotFoundException) {
    response.setStatus(404);
    error.put("message", "请求的资源不存在");
} else if (ex instanceof AccessDeniedException) {
    response.setStatus(403);
    error.put("message", "无权访问");
}

@ControllerAdvice 对比:

特性 HandlerExceptionResolver @ControllerAdvice + @ExceptionHandler
灵活性 更底层,可完全控制处理流程 基于注解,较为简洁
适用场景 需要动态判断响应类型(如 HTML/JSON) 同一异常类型固定返回一种响应格式
代码侵入性 需手动注册解析器 无侵入,通过注解声明
优先级控制 通过 Ordered 接口精细控制 默认按 @Order 注解或 Bean 顺序

总结

HandlerExceptionResolver 是 Spring MVC 异常处理的底层核心机制,适合需要高度定制异常响应的场景。通过本文的实例,你可以快速掌握:

  • 如何区分 Web 和 API 请求返回不同格式的错误信息

  • 如何结合日志记录、异常分类提升系统可维护性

  • 灵活控制处理优先级和响应细节

尽管现代 Spring 项目中更推荐使用 @ControllerAdvice(代码更简洁),但在需要动态处理逻辑(如多端适配、第三方库异常转换)时,HandlerExceptionResolver 仍是不可或缺的利器。

相关推荐
wuminyu1 小时前
专家视角看Java字节码加载与存储指令机制
java·linux·c语言·jvm·c++
callJJ2 小时前
Spring Data Redis 两种编程模型详解:同步 vs 响应式
java·spring boot·redis·python·spring
phltxy3 小时前
Spring Cloud 分布式服务部署实战:从 0 到 1 实现微服务上线
spring·spring cloud·微服务
wbs_scy3 小时前
Linux线程同步与互斥(三):线程同步深度解析之POSIX 信号量与环形队列生产者消费者模型,从原理到源码彻底吃透
java·开发语言
jinanwuhuaguo4 小时前
(第三十三篇)五月的文明奠基:OpenClaw 2026.5.2版本的文明级解读
android·java·开发语言·人工智能·github·拓扑学·openclaw
xmjd msup5 小时前
spring security 超详细使用教程(接入springboot、前后端分离)
java·spring boot·spring
952365 小时前
SpringBoot统一功能处理
java·spring boot·后端
Lyyaoo.6 小时前
优惠券秒杀业务分析
java·开发语言
消失的旧时光-19436 小时前
统一并发模型:线程、Reactor、协程本质是一件事(从线程到协程 · 第6篇·终章)
java·python·算法
勿忘初心12216 小时前
Java 国密 SM4 加密工具类实战(Hutool + BouncyCastle)|企业级数据加密 + 兼容 JDK8
java·数据安全·数据加密·后端开发·企业级开发·国密 sm4