深入解析:Spring Boot全局异常处理:一网打尽Controller层异常,@RestControllerAdvice解析
在Spring Boot应用中,异常处理是构建健壮API的核心环节。
一、为什么需要全局异常处理?
在传统Controller开发中,我们经常面临这样的困境:
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
try {
return userService.findById(id);
} catch (UserNotFoundException e) {
// 重复的异常处理代码
return new ErrorResponse("用户不存在");
} catch (DatabaseException e) {
// 每个Controller都要处理相同异常
return new ErrorResponse("数据库错误");
}
}
}
痛点分析:
重复代码遍布各个Controller
异常处理与业务逻辑耦合
响应格式不统一
维护成本高
二、@RestControllerAdvice
1. 注解关系
2. 核心组件协作
三、Spring Boot 3 全局异常处理实战
1. 基础异常响应封装
public record ErrorResponse(
Instant timestamp,
int status,
String error,
String message,
String path
) {
public ErrorResponse(HttpStatus status, String message, String path) {
this(Instant.now(), status.value(), status.getReasonPhrase(), message, path);
}
}
2. 核心异常处理器实现
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理自定义业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity handleBusinessException(
BusinessException ex, WebRequest request) {
return createResponse(HttpStatus.BAD_REQUEST, ex, request);
}
// 处理数据不存在异常
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity handleEntityNotFound(
EntityNotFoundException ex, WebRequest request) {
return createResponse(HttpStatus.NOT_FOUND, ex, request);
}
// 处理所有未捕获异常
@ExceptionHandler(Exception.class)
public ResponseEntity handleAllExceptions(
Exception ex, WebRequest request) {
return createResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex, request);
}
// 统一响应构建方法
private ResponseEntity createResponse(
HttpStatus status, Exception ex, WebRequest request) {
String path = ((ServletWebRequest) request).getRequest().getRequestURI();
ErrorResponse body = new ErrorResponse(status, ex.getMessage(), path);
return ResponseEntity.status(status).body(body);
}
}
3. 自定义业务异常
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}
四、深度解析异常处理流程
1. 异常处理调用链
2. 异常匹配优先级
五、高级应用场景
1. 处理验证异常(Spring Validation)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorResponse handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error -> {
String fieldName = error.getField();
String message = error.getDefaultMessage();
errors.put(fieldName, message);
});
return new ErrorResponse(
HttpStatus.BAD_REQUEST,
"验证失败",
errors
);
}
2. 处理安全异常(Spring Security)
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity handleAccessDenied(
AccessDeniedException ex, WebRequest request) {
String path = ((ServletWebRequest) request).getRequest().getRequestURI();
ErrorResponse body = new ErrorResponse(
HttpStatus.FORBIDDEN,
"无权访问此资源",
path
);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(body);
}
六、性能优化最佳实践
异常分类处理:精确匹配特定异常,避免使用过于宽泛的Exception.class
层次化异常结构:
// 基础业务异常 public abstract class BaseException extends RuntimeException { private final ErrorCode errorCode; public BaseException(ErrorCode errorCode, String message) { super(message); this.errorCode = errorCode; } } // 具体业务异常 public class PaymentException extends BaseException { public PaymentException(String message) { super(ErrorCode.PAYMENT_FAILED, message); } }
异常日志分级:
@ExceptionHandler(BusinessException.class) public ResponseEntity handleBusinessException( BusinessException ex, WebRequest request) { // 业务异常只记录DEBUG日志 log.debug("Business exception occurred: {}", ex.getMessage()); return createResponse(HttpStatus.BAD_REQUEST, ex, request); } @ExceptionHandler(Exception.class) public ResponseEntity handleAllExceptions( Exception ex, WebRequest request) { // 未知异常记录ERROR日志 log.error("Unexpected error occurred", ex); return createResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex, request); }
七、实战
正常请求:
GET /api/users/123
Status: 200 OK
Response: {"id":123,"name":"John Doe"}
异常请求
GET /api/users/999
Status: 404 Not Found
Response: {
"timestamp": "2023-08-20T10:15:30Z",
"status": 404,
"error": "Not Found",
"message": "用户不存在",
"path": "/api/users/999"
}
验证失败请求:
POST /api/users
Payload: {"name": "", "email": "invalid-email"}
Status: 400 Bad Request
Response: {
"timestamp": "2023-08-20T10:18:45Z",
"status": 400,
"error": "Bad Request",
"message": "验证失败",
"errors": {
"name": "姓名不能为空",
"email": "邮箱格式不正确"
}
}
八、总结
1、@RestControllerAdvice = @ControllerAdvice + @ResponseBody
- 全局处理控制器异常
- 响应体自动序列化(JSON/XML)
2、异常处理优先级:
- Controller局部处理器 > 全局处理器
- 具体异常 > 通用异常
3.最佳实践:
// 返回明确HTTP状态码
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(UserNotFoundException.class)
// 使用ResponseEntity精确控制响应
@ExceptionHandler(PaymentException.class)
public ResponseEntity handlePaymentException() {
// 自定义响应头等复杂操作
}
4、生产环境建议:
- 禁用敏感错误信息(spring.mvc.include-error-details)
- 自定义错误码体系
- 异常国际化支持
So, 当再次处理错误时,应该能独挡一面了。当然,业务中,异常还应该有日志记录,如ES,traceInfo, 上下文等信息,便于排错.