本文目录导读:

我们来详细讲解一下如何编写一个全局异常处理器,这通常是在使用 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。
- 信息安全:不向用户暴露系统细节。
按照上面的模板,你就可以快速搭建一个健壮、可维护的全局异常处理机制了。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播