在 Web 应用中,异常是不可避免的。用户的输入不合法,服务的某部分出错,或者数据库连接失败,这些情况都可能触发异常。那么问题来了:如何优雅地捕获并处理这些异常,让用户体验不至于因为一时的错误而受损?


Spring MVC 提供了灵活而强大的异常处理机制,包括局部异常处理和全局异常处理,帮助我们轻松管理这些 “意外情况”。接下来,我们就来详细聊聊如何用 Spring MVC 的异常处理机制,做一个从容应对错误的开发者!


# 1. 异常处理的两种方式

# 1.1 局部异常处理:Controller 内部的 “急救” 机制

如果某些异常仅与特定的控制器相关,我们可以直接在该 Controller 中捕获并处理这些异常。

@ExceptionHandler 注解用于指定一个方法处理某种特定的异常。这种方式的好处是逻辑清晰,控制器自己的异常自己处理。

示例:局部异常处理

@Controller
@RequestMapping("/user")
public class UserController {
    @GetMapping("/{id}")
    public String getUser(@PathVariable int id) {
        if (id <= 0) {
            throw new IllegalArgumentException("Invalid user ID");
        }
        return "userView";
    }
    @ExceptionHandler(IllegalArgumentException.class)
    public String handleIllegalArgumentException(IllegalArgumentException e, Model model) {
        model.addAttribute("errorMessage", e.getMessage());
        return "error";
    }
}

运行效果

  • 当用户访问 /user/-1 时,抛出的 IllegalArgumentException 会被 handleIllegalArgumentException 方法捕获。
  • 返回错误页面 error ,并在页面上显示错误信息 Invalid user ID

# 1.2 全局异常处理:让异常不再 “局限于一隅”

对于跨多个 Controller 的通用异常,例如权限校验失败、参数格式不合法等,局部处理显然不够灵活。此时,我们可以使用全局异常处理机制

关键注解: @ControllerAdvice

  • @ControllerAdvice 是 Spring MVC 提供的全局异常处理工具。
  • 它会扫描所有标注了 @Controller 的类,捕获其中抛出的异常。

示例:全局异常处理

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(IllegalArgumentException.class)
    public String handleIllegalArgumentException(IllegalArgumentException e, Model model) {
        model.addAttribute("errorMessage", e.getMessage());
        return "error";
    }
    @ExceptionHandler(Exception.class)
    public String handleGenericException(Exception e, Model model) {
        model.addAttribute("errorMessage", "An unexpected error occurred: " + e.getMessage());
        return "error";
    }
}

运行效果

  • 不管哪个 Controller 抛出 IllegalArgumentException ,都会由 handleIllegalArgumentException 方法处理。
  • 如果捕获不到具体的异常类型,就可以指定为 Exception 异常大类,捕获到之后进入通用异常处理逻辑

# 2. 实践中的常见用法

# 2.1 返回 JSON 格式的错误信息

前后端分离的项目中,返回错误页面显然不是最佳选择。通过 @ResponseBody **@RestControllerAdvice**(@ControllerAdvice@ResponseBody 的组合注解 ,可以直接返回 JSON 格式的错误信息。可以返回一个对象或者 Map 集合来自动处理成 JSON 格式返回

示例:全局 JSON 异常处理

@RestControllerAdvice
public class GlobalRestExceptionHandler {
    @ExceptionHandler(IllegalArgumentException.class)
    public Map<String, Object> handleIllegalArgumentException(IllegalArgumentException e) {
        Map<String, Object> response = new HashMap<>();
        response.put("error", true);
        response.put("message", e.getMessage());
        return response;
    }
    @ExceptionHandler(Exception.class)
    public Map<String, Object> handleGenericException(Exception e) {
        Map<String, Object> response = new HashMap<>();
        response.put("error", true);
        response.put("message", "An unexpected error occurred");
        return response;
    }
}

运行效果

例如请求 /user/-1 ,则会返回:

{
    "error": true,
    "message": "Invalid user ID"
}

# 2.2 自定义异常与枚举

将异常信息和状态码绑定到自定义异常中,让错误响应更加规范化。

定义枚举:错误类型与状态码

public enum ErrorCode {
    INVALID_USER_ID(400, "Invalid user ID"),
    SERVER_ERROR(500, "Internal Server Error");
    private final int code;
    private final String message;
    ErrorCode(int code, String message) {
        this.code = code;
        this.message = message;
    }
    public int getCode() {
        return code;
    }
    public String getMessage() {
        return message;
    }
}

自定义异常

public class CustomException extends RuntimeException {
    private final ErrorCode errorCode;
    public CustomException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
    }
    public int getCode() {
        return errorCode.getCode();
    }
}

统一异常处理

@RestControllerAdvice
public class GlobalCustomExceptionHandler {
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<Map<String, Object>> handleCustomException(CustomException e) {
        Map<String, Object> response = new HashMap<>();
        response.put("error", true);
        response.put("code", e.getCode());
        response.put("message", e.getMessage());
        return new ResponseEntity<>(response, HttpStatus.valueOf(e.getCode()));
    }
}

【我们来对其中的一些代码片段做一些解释】


返回值 ResponseEntity<Map<String, Object>> 说明

  • ResponseEntity 是 Spring 提供的响应封装类,用于设置 HTTP 状态码和响应体。其中 ** HttpStatus.valueOf(e.getCode())是 根据异常中的状态码 ** 设置的 HTTP 响应的状态码
  • Map<String, Object>响应体的数据结构,存储自定义的错误信息。

* 注意区别 *:HTTP 响应状态码和 JSON 响应体中的状态码是 * 两种不同的内容 *

运行效果

  • 在一些逻辑判断之后,手动抛出自定义异常:

    throw new CustomException(ErrorCode.INVALID_USER_ID);
  • 响应结果:

    {
        "error": true,
        "code": 400,
        "message": "Invalid user ID"
    }

# 3. 局部与全局处理的对比

特性 局部异常处理 全局异常处理
适用范围 单个 Controller 中 所有 Controller
注解 @ExceptionHandler @ControllerAdvice
优点 定制化处理,逻辑清晰 统一处理异常,代码更简洁
缺点 只能处理局部异常,扩展性差 可能需要额外判断 Controller 作用域

# 4. 总结

  1. 局部异常处理:使用 @ExceptionHandler 捕获特定 Controller 的异常,适合个性化逻辑需求。
  2. 全局异常处理:通过 @ControllerAdvice 实现全局异常管理,特别适用于通用异常类型。
  3. 高级用法:结合 @RestControllerAdvice 返回 JSON 错误信息,自定义异常类与状态码,让接口更规范。

在实际项目中,局部与全局处理可以结合使用。局部处理特殊逻辑,全局处理通用错误,真正做到优雅应对各种 “意外”。