全局异常处理架构
框架采用 Spring 的 @RestControllerAdvice 机制实现全局异常拦截,核心类架构如下:
核心类结构
GlobalControllerAdvice (具体实现)
↓ 继承
BaseControllerAdvice (抽象基类)
↓ 实现
LoggingComponent (日志组件接口)BaseControllerAdvice - 异常处理基类
提供异常处理的通用能力:
- 日志格式化:
printStackTraceFormat()- 统一格式化异常日志输出 - 请求信息获取:
getRequestInfo()- 自动获取请求上下文 - 校验错误解析:
parseAndPrintBindErrorMessage()- 解析 Bean Validation 错误 - 日志组件集成:实现
LoggingComponent,统一日志标签为KS-ControllerAdvice
日志输出格式:
log
[KS-ControllerAdvice] {异常类型} [requestUri: /api/user, requestMethod: POST, clientIp: 192.168.1.100, requestInfo: {...}, errorMessage: xxx]GlobalControllerAdvice - 全局异常处理器
继承 BaseControllerAdvice,使用 @ExceptionHandler 注解捕获各类异常:
| 异常处理方法 | 捕获异常 | 日志级别 | 说明 |
|---|---|---|---|
generalErrorHandle | ApplicationException | WARN | 业务异常(自定义) |
thirdPartyErrorHandler | ThirdPartyException | ERROR | 第三方服务异常 |
stackTracerErrorHandler | StackTraceException | ERROR | 堆栈异常(打印完整堆栈) |
validationExceptionHandler | MethodArgumentNotValidException | WARN | @Valid 校验失败 |
validationExceptionHandler | BindException | WARN | 参数绑定异常 |
validationExceptionHandler | ValidationException | WARN | JSR-303 校验异常 |
validationExceptionHandler | HttpMessageNotReadableException | WARN | JSON 解析失败 |
validationExceptionHandler | MethodArgumentTypeMismatchException | WARN | 参数类型转换失败 |
validationExceptionHandler | MissingServletRequestParameterException | WARN | 必填参数缺失 |
validationExceptionHandler | HttpRequestMethodNotSupportedException | WARN | 请求方法不支持 |
serverErrorHandler | Throwable | ERROR | 兜底处理所有未知异常 |
异常日志详解
每个异常都会输出详细的上下文信息,便于排查问题:
业务异常日志示例(ApplicationException):
log
[KS-ControllerAdvice] -------------------------------- 自定义异常信息 -------------------------------- [Begin]
[KS-ControllerAdvice] 自定义异常信息 [requestUri: /api/user/login, requestMethod: POST, clientIp: 192.168.1.100, requestInfo: {"userId":1001}, errorMessage: 用户密码错误]
[KS-ControllerAdvice] -------------------------------- 自定义异常信息 -------------------------------- [ End ]参数校验异常日志示例(@Valid):
log
[KS-ControllerAdvice] -------------------------------- 请求体验证异常 -------------------------------- [Begin]
[KS-ControllerAdvice] 请求体验证异常 [requestUri: /api/user/register, requestMethod: POST, clientIp: 192.168.1.100, requestInfo: {...}, errorType: @NotBlank, errorMessage: 用户名不能为空, field: username=null]
[KS-ControllerAdvice] -------------------------------- 请求体验证异常 -------------------------------- [ End ]系统异常日志示例(未知异常):
log
[KS-ControllerAdvice] -------------------------------- 系统异常信息 -------------------------------- [Begin]
[KS-ControllerAdvice] 系统异常信息 [requestUri: /api/order/create, requestMethod: POST, clientIp: 192.168.1.100, requestInfo: {...}, errorMessage: NullPointerException: Cannot invoke method on null object]
[完整堆栈信息...]
[KS-ControllerAdvice] -------------------------------- 系统异常信息 -------------------------------- [ End ]自定义异常处理
方式1:重写异常处理方法
继承 GlobalControllerAdvice 并重写对应的方法:
java
import host.springboot.framework.context.advice.GlobalControllerAdvice;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import jakarta.servlet.http.HttpServletRequest;
@RestControllerAdvice
public class MyControllerAdvice extends GlobalControllerAdvice {
@Override
public BaseVO generalErrorHandle(
ApplicationException e,
HttpServletRequest request
) {
// 自定义处理逻辑
log().error("自定义日志: {}", e.getMessage());
return R.base(e.getErrorEnum(), "自定义提示");
}
}💡 注意:
- 自定义 Advice 会自动替换框架默认的
GlobalControllerAdvice - 仅重写需要自定义的方法,其他依然使用父类实现
- 可以通过
log()方法使用框架提供的日志组件
方式2:添加新的异常处理方法
在自定义 Advice 中添加 @ExceptionHandler 方法:
java
@RestControllerAdvice
public class MyControllerAdvice extends GlobalControllerAdvice {
// 处理自定义业务异常
@ExceptionHandler(CustomBusinessException.class)
public BaseVO handleCustomBusinessException(
CustomBusinessException e,
HttpServletRequest request
) {
String clientIp = IpUtils.getIpv4Address(request);
log().warn("[自定义业务异常] [requestUri: {}, clientIp: {}, message: {}]",
request.getRequestURI(), clientIp, e.getMessage());
return R.base(e.getErrorCode(), e.getMessage(), e.getUserTip());
}
// 处理特定业务场景异常
@ExceptionHandler(IllegalStateException.class)
public BaseVO handleIllegalStateException(
IllegalStateException e,
HttpServletRequest request
) {
log().warn("[状态异常] [uri: {}] {}",
request.getRequestURI(), e.getMessage());
return R.base(
ErrorCodeEnum.USER_REQUEST_PARAMETER_ERROR,
"操作失败,请稍后重试"
);
}
}启用方式
方式1:自动配置(推荐)
引入 krismile-boot3-autoconfigure 依赖后自动生效,无需任何配置。
方式2:显式启用
在 Spring Boot 应用主类上添加 @EnableGlobalControllerAdvice 注解:
java
import host.springboot.framework.context.advice.annotation.EnableGlobalControllerAdvice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableGlobalControllerAdvice
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}最佳实践
✅ 推荐做法
java
// 1. 继承 GlobalControllerAdvice 复用基础能力
@RestControllerAdvice
public class CustomAdvice extends GlobalControllerAdvice {
// 仅添加或重写需要自定义的方法
}
// 2. 使用父类的日志组件
log().warn("业务异常: {}", e.getMessage());
// 3. 复用父类的工具方法
String requestInfo = getRequestInfo(request);
String errorMsg = printStackTraceFormat(...);
// 4. 保持统一的响应格式
return R.base(errorCode, message, userTip);❌ 不推荐做法
java
// ❌ 不要直接使用 @RestControllerAdvice 而不继承 GlobalControllerAdvice
@RestControllerAdvice
public class MyAdvice {
// 失去了框架提供的基础能力
}
// ❌ 不要在 Advice 中抛出新的异常
@ExceptionHandler(Exception.class)
public BaseVO handle(Exception e) {
throw new RuntimeException("处理失败"); // 会导致无限循环
}
// ❌ 不要忽略异常不返回响应
@ExceptionHandler(Exception.class)
public void handle(Exception e) {
// 没有返回值会导致客户端收不到响应
}常见问题
Q: 自定义 Advice 后框架的异常处理失效了?
A: 确保自定义 Advice 继承了 GlobalControllerAdvice,否则会完全覆盖框架的异常处理。
java
// ✅ 正确:继承 GlobalControllerAdvice
@RestControllerAdvice
public class MyAdvice extends GlobalControllerAdvice { }
// ❌ 错误:不继承会失去框架能力
@RestControllerAdvice
public class MyAdvice { }Q: 如何在 Advice 中获取当前用户信息?
A: 通过 RequestInfo.REQUEST_CONTEXT 或 HttpServletRequest 获取:
java
@ExceptionHandler(ApplicationException.class)
public BaseVO handle(ApplicationException e, HttpServletRequest request) {
// 方式1:从 ThreadLocal 获取
RequestInfo requestInfo = RequestInfo.REQUEST_CONTEXT.get();
// 方式2:从 request attribute 获取
Object info = request.getAttribute(RequestInfo.class.getSimpleName());
// 方式3:使用父类方法
String requestInfoJson = getRequestInfo(request);
return R.base(e.getErrorEnum(), e.getUserTip());
}Q: 多个 @RestControllerAdvice 的执行顺序?
A: 使用 @Order 注解控制优先级(数字越小优先级越高):
java
import org.springframework.core.annotation.Order;
@Order(1) // 优先级最高
@RestControllerAdvice
public class HighPriorityAdvice extends GlobalControllerAdvice { }
@Order(100) // 优先级较低
@RestControllerAdvice
public class LowPriorityAdvice extends GlobalControllerAdvice { }💡 提示:通常不需要多个 Advice,建议在一个自定义 Advice 中集中处理所有异常。
