Skip to content

全局异常处理架构

框架采用 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 注解捕获各类异常:

异常处理方法捕获异常日志级别说明
generalErrorHandleApplicationExceptionWARN业务异常(自定义)
thirdPartyErrorHandlerThirdPartyExceptionERROR第三方服务异常
stackTracerErrorHandlerStackTraceExceptionERROR堆栈异常(打印完整堆栈)
validationExceptionHandlerMethodArgumentNotValidExceptionWARN@Valid 校验失败
validationExceptionHandlerBindExceptionWARN参数绑定异常
validationExceptionHandlerValidationExceptionWARNJSR-303 校验异常
validationExceptionHandlerHttpMessageNotReadableExceptionWARNJSON 解析失败
validationExceptionHandlerMethodArgumentTypeMismatchExceptionWARN参数类型转换失败
validationExceptionHandlerMissingServletRequestParameterExceptionWARN必填参数缺失
validationExceptionHandlerHttpRequestMethodNotSupportedExceptionWARN请求方法不支持
serverErrorHandlerThrowableERROR兜底处理所有未知异常

异常日志详解

每个异常都会输出详细的上下文信息,便于排查问题:

业务异常日志示例(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 中集中处理所有异常。

Released under the Apache-2.0 License