本文目录导读:

我们来详细讲解一下如何编写一个全局异常处理器,这通常是在使用 Spring Boot(或其他 Java Web 框架)时进行统一错误处理的标准做法。
我将以 Spring Boot 中最常用的 @RestControllerAdvice 和 @ExceptionHandler 注解组合为例,为你提供一套完整、规范的写法。
核心思想
全局异常处理器的主要目标是:
- 统一捕获:捕获所有 Controller 层抛出的未处理异常。
- 统一格式:将异常信息转换为统一的、对前端友好的 JSON 响应体(
{ "code": 500, "message": "服务器内部错误", "data": null })。 - 解耦逻辑:业务代码中只抛出特定异常(如
BusinessException),处理器负责将其翻译成对应的 HTTP 状态码和错误信息。
完整代码实战
定义统一的响应结果类(非常重要)
你需要一个标准的 API 响应格式,这样前端可以统一解析。
import lombok.Data;
@Data
public class ApiResponse<T> {
private int code;
private String message;
private T data;
// 成功的静态方法
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(200);
response.setMessage("success");
response.setData(data);
return response;
}
// 失败的静态方法
public static <T> ApiResponse<T> fail(int code, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(code);
response.setMessage(message);
response.setData(null);
return response;
}
}
定义自定义业务异常类
为了让异常分类更清晰,建议创建自己的业务异常。
import lombok.Getter;
@Getter
public class BusinessException extends RuntimeException {
private int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
// 可以预定义一些常用的构造函数
public BusinessException(String message) {
super(message);
this.code = 400; // 默认业务异常为 400 Bad Request
}
}
编写核心的全局异常处理器(GlobalExceptionHandler)
这是核心部分,使用 @RestControllerAdvice 注解,它结合了 @ControllerAdvice + @ResponseBody。
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;
@Slf4j // 使用Lombok记录日志
@RestControllerAdvice // 拦截所有 @RestController 的异常
public class GlobalExceptionHandler {
// ==================== 1. 捕获自定义的业务异常 ====================
@ExceptionHandler(BusinessException.class)
public ApiResponse<Void> handleBusinessException(BusinessException e) {
log.warn("业务异常: code={}, message={}", e.getCode(), e.getMessage());
// 使用自定义的 code 和 message 返回
return ApiResponse.fail(e.getCode(), e.getMessage());
}
// ==================== 2. 捕获参数校验异常(@Valid / @Validated) ====================
// 处理 @RequestBody 上的参数校验失败
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST) // 返回 400 状态码
public ApiResponse<Void> handleValidationException(MethodArgumentNotValidException e) {
// 提取所有字段的错误信息,用逗号拼接
String errorMsg = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.warn("参数校验失败: {}", errorMsg);
return ApiResponse.fail(400, errorMsg);
}
// 处理 @RequestParam / @PathVariable 上的参数校验失败
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> handleConstraintViolationException(ConstraintViolationException e) {
String errorMsg = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(", "));
log.warn("参数校验失败: {}", errorMsg);
return ApiResponse.fail(400, errorMsg);
}
// ==================== 3. 捕获资源未找到异常 ====================
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND) // 返回 404
public ApiResponse<Void> handleNotFoundException(NoHandlerFoundException e) {
log.warn("资源未找到: {}", e.getRequestURL());
return ApiResponse.fail(404, "请求的资源不存在");
}
// ==================== 4. 捕获最顶级的通用异常(兜底) ====================
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 返回 500
public ApiResponse<Void> handleException(Exception e) {
// 这里一定要记错误日志,方便排查问题
log.error("系统内部异常: ", e);
// 避免把具体的异常堆栈暴露给前端
return ApiResponse.fail(500, "服务器内部错误,请联系管理员");
}
}
如何使用(示例 Controller)
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@RestController
@RequestMapping("/user")
public class UserController {
// 1. 业务异常示例
@GetMapping("/{id}")
public ApiResponse<User> getUser(@PathVariable Long id) {
if (id <= 0) {
throw new BusinessException(1001, "用户ID不能为负数");
}
// 正常查询...
User user = new User(); // 假设查到了
return ApiResponse.success(user);
}
// 2. 参数校验示例(配合 @Valid)
@PostMapping("/create")
public ApiResponse<Void> createUser(@Valid @RequestBody UserCreateRequest request) {
// request 的 name 为空,会触发 MethodArgumentNotValidException
System.out.println("创建用户: " + request.getName());
return ApiResponse.success(null);
}
// 3. 未捕获的异常示例(会触发兜底的 handleException)
@GetMapping("/error")
public ApiResponse<Void> triggerError() {
int result = 1 / 0; // 抛出 ArithmeticException
return ApiResponse.success(null);
}
}
// 请求体 DTO
class UserCreateRequest {
@NotBlank(message = "用户名不能为空")
private String name;
@NotNull(message = "年龄不能为空")
private Integer age;
// getters and setters...
}
进阶与优化建议
-
异常来源区分:
- 业务异常:用户操作不当(如余额不足),直接返回给用户提示。
- 系统异常:代码 Bug、数据库连接失败,记录详细日志,但只返回“系统繁忙”给用户。
-
日志级别:
BusinessException:用log.warn,因为这是预期内的问题。Exception(兜底):用log.error,并打印完整堆栈 (log.error("...", e))。
-
安全问题:
- 不要在返回给前端的
message中暴露数据库表名、SQL 错误、文件路径等敏感信息。 DataIntegrityViolationException(数据库约束违反)应该被转换为“数据操作失败”,而不是“Duplicate entry 'xxx' for key 'user.email'”。
- 不要在返回给前端的
-
使用枚举管理错误码:创建一个
ErrorCode枚举,避免在代码中写死数字400、500。public enum ErrorCode { SUCCESS(200, "success"), BAD_REQUEST(400, "请求参数错误"), UNAUTHORIZED(401, "未登录"), NOT_FOUND(404, "资源不存在"), INTERNAL_ERROR(500, "服务器内部错误"), BUSINESS_ERROR(1001, "业务逻辑错误"); private int code; private String message; // 构造方法、getter... } -
处理
HttpMessageNotReadableException:用户传了无法解析的 JSON 时,返回400和清晰的提示。
一个优秀的全局异常处理器应该做到:
- 分层捕获:自定义异常 -> 框架异常(参数校验、404等) -> 通用异常。
- 统一响应:所有异常都返回结构一致的 JSON。
- 日志分级:业务异常 warn,系统异常 error。
- 信息安全:不向用户暴露系统细节。
按照上面的模板,你就可以快速搭建一个健壮、可维护的全局异常处理机制了。