Spring Boot 异常处理全链路解析

1. 前言

在 Spring Boot 开发中,我们习惯使用 @RestControllerAdvice@ExceptionHandler 来进行全局异常处理。但你是否思考过以下问题:

  • 当 Controller 抛出异常时,是谁捕获了它?
  • 为什么我们在类上加个注解@RestControllerAdvice,Spring 就能自动调用我们的方法?
  • GlobalExceptionHandler 到底是被谁调用的?
  • 为什么 Filter 中的异常默认无法被捕获?

本文将剥离表象,深入 DispatcherServletHandlerExceptionResolver 源码,还原一条清晰的异常处理逻辑链条。


2. 核心组件全景图

在深入代码之前,我们先理清几个核心角色的关系:

  1. DispatcherServlet : 前端控制器。它是异常处理的发起者,负责捕获 Controller 抛出的异常,并委托给解析器处理。
  2. HandlerExceptionResolver : 异常解析器接口。定义了"如何处理异常"的标准。
  3. ExceptionHandlerExceptionResolver : 接口的核心实现类 。它专门负责处理 @ExceptionHandler 注解。
  4. GlobalExceptionHandler : 开发者编写的业务类。它包含了具体的异常处理逻辑,是被调用的目标。

逻辑链路简述:
DispatcherServlet (捕获异常) -> 遍历 HandlerExceptionResolver 列表 -> 找到 ExceptionHandlerExceptionResolver -> 反射调用 GlobalExceptionHandler -> 写入 JSON 响应。


3. 逻辑起点:DispatcherServlet 的捕获机制

一切始于 Spring MVC 的核心中枢 DispatcherServlet。在请求分发过程中(doDispatch),它包裹了整个业务流程的执行。

源码逻辑简化(DispatcherServlet.java)

java 复制代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    Exception dispatchException = null;
    try {
        // 1. 获取 Handler (Controller)
        // 2. 执行 Handler (调用你的 Controller 方法)
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    } catch (Exception ex) {
        // 3. 【关键点】捕获 Controller 抛出的所有异常
        dispatchException = ex;
    }
    
    // 4. 处理分发结果(包含异常处理逻辑)
    processDispatchResult(request, response, mappedHandler, mv, dispatchException);
}

private void processDispatchResult(..., Exception ex) {
    if (ex != null) {
        // 5. 如果存在异常,委托给异常解析器处理
        processHandlerException(request, response, handler, ex);
    }
}

结论: DispatcherServlet 是异常处理的入口 ,它通过 try-catch 兜底了 Controller 的运行。


4. 核心委托:processHandlerException

当捕获到异常后,DispatcherServlet 并不是自己处理,而是遍历容器中所有的 HandlerExceptionResolver

源码逻辑简化

java 复制代码
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, 
                                               Object handler, Exception ex) {
    // handlerExceptionResolvers 是 Spring Boot 启动时自动注入的 List
    if (this.handlerExceptionResolvers != null) {
        // 1. 遍历所有解析器
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            // 2. 尝试解析
            ModelAndView exMv = resolver.resolveException(request, response, handler, ex);
            // 3. 只要有一个解析器返回了非空结果,就认为处理成功,中断循环
            if (exMv != null) {
                return exMv;
            }
        }
    }
    // ... 如果都没处理,则继续抛出
}

谁实现了 HandlerExceptionResolver?

Spring Boot WebMvcAutoConfiguration 会默认自动配置以下实现类(按优先级排序):

  1. ExceptionHandlerExceptionResolver :处理 @ExceptionHandler(最重要)。
  2. ResponseStatusExceptionResolver:处理 @ResponseStatus
  3. DefaultHandlerExceptionResolver:处理 Spring 内部标准异常(如 405 Method Not Allowed)。

5. 主力实现:ExceptionHandlerExceptionResolver

这是本文的主角。它的职责是连接"Spring 框架"和"开发者代码"。

5.1 初始化阶段 (Startup)

当 Spring 容器启动时,ExceptionHandlerExceptionResolver 会扫描所有标注了 @ControllerAdvice@RestControllerAdvice 的 Bean(即你的 GlobalExceptionHandler),并将其中标注了 @ExceptionHandler 的方法缓存起来。

  • Map Key : 异常类型(如 ServiceException.class
  • Map Value : 执行方法(如 GlobalExceptionHandler.serviceExceptionHandler()

5.2 运行时阶段 (Runtime)

DispatcherServlet 调用 resolver.resolveException() 时,实际执行的是 ExceptionHandlerExceptionResolver 的逻辑:

java 复制代码
// ExceptionHandlerExceptionResolver.java
@Override
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, 
                                                       HttpServletResponse response, 
                                                       HandlerMethod handlerMethod, 
                                                       Exception exception) {
    
    // 1. 【寻址】根据异常类型,从缓存中找到对应的处理方法
    // 例如:ServiceException -> 你的 serviceExceptionHandler 方法
    ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);

    if (exceptionHandlerMethod == null) {
        return null; // 找不到处理方法,返回 null,交给下一个解析器
    }

    // 2. 【设置转换器】配置返回值处理器
    // 因为你的 GlobalExceptionHandler 标了 @ResponseBody,
    // 这里会使用 RequestResponseBodyMethodProcessor 负责将结果转为 JSON
    exceptionHandlerMethod.setReturnValueHandlers(getReturnValueHandlers());

    // 3. 【反射调用】执行你的 GlobalExceptionHandler 方法
    exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception);

    // 4. 返回空的 ModelAndView
    // 因为 JSON 已经写入 Response 流了,不需要 View 解析
    return new ModelAndView();
}

6. 开发者的实现:GlobalExceptionHandler

这是位于链路末端的业务代码。它本身不具备拦截能力,它只是一个被反射调用的目标对象

java 复制代码
@RestControllerAdvice // 1. 标记该 Bean 需要被 ExceptionHandlerExceptionResolver 扫描
public class GlobalExceptionHandler {

    // 2. 定义处理逻辑
    @ExceptionHandler(ServiceException.class)
    public CommonResult<?> serviceExceptionHandler(ServiceException ex) {
        // 返回业务对象
        return CommonResult.error(ex.getCode(), ex.getMessage());
    }
    
    @ExceptionHandler(Exception.class)
    public CommonResult<?> defaultExceptionHandler(Exception ex) {
        return CommonResult.error(500, "系统内部异常");
    }
}

7. 逻辑闭环:从异常到 JSON

让我们把所有环节串联起来,看一个完整的执行流:

  1. 抛出 :Controller 执行业务逻辑,throw new ServiceException(404, "用户未找到")
  2. 捕获DispatcherServlettry-catch 块捕获该异常。
  3. 分发DispatcherServlet 遍历 Resolvers,调用 resolveException
  4. 定位ExceptionHandlerExceptionResolver 介入,通过异常类型 ServiceException 查找缓存,定位到 GlobalExceptionHandler.serviceExceptionHandler 方法。
  5. 调用 :通过反射执行该方法,得到返回值 CommonResult 对象。
  6. 转换RequestResponseBodyMethodProcessor 调用 Jackson 的 ObjectMapper,将 CommonResult 序列化为 JSON 字符串。
  7. 写入 :JSON 字符串被写入 HttpServletResponse 的输出流。
  8. 结束DispatcherServlet 认为请求处理完成。

8. @RestControllerAdvice和@ControllerAdvice区别和联系

就像 @RestController@Controller@ResponseBody 的组合一样,@RestControllerAdvice 也是一个组合注解。

8.1. 核心区别 (Difference)

特性 @ControllerAdvice @RestControllerAdvice
引入版本 Spring 3.2 Spring 4.3
核心组成 仅包含 @Component 功能 包含 @ControllerAdvice + @ResponseBody
默认响应行为 视图解析 (View Resolution);默认行为是跳转页面 (JSP/Thymeleaf)。 写入响应体 (Response Body);默认行为是返回数据 (JSON/XML)。
适用场景 传统 MVC 应用(前后端不分离,返回 HTML)。 RESTful API 应用(前后端分离,返回 JSON)。
写 JSON 的方式 需要在每个方法上额外加 @ResponseBody 不需要,类上自带了,所有方法默认生效。

8.2. 代码对比 (Code Comparison)

假设我们要实现一个全局异常处理,返回 JSON 格式的错误信息。

场景 A:使用 @ControllerAdvice (麻烦的写法)

如果你用 @ControllerAdvice,但想返回 JSON,你必须在每个 @ExceptionHandler 方法上(或者类上)手动添加 @ResponseBody,否则 Spring 会尝试去找一个叫 "User Not Found" 的 HTML 页面。

java 复制代码
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    @ResponseBody  // <--- 必须加这个,否则报错或尝试跳转视图
    public CommonResult<String> handleUserNotFound(UserNotFoundException e) {
        return CommonResult.error(404, e.getMessage());
    }
}
场景 B:使用 @RestControllerAdvice (推荐的写法)

如果你用 @RestControllerAdvice,由于它内部已经包含了 @ResponseBody,你只需要关注异常处理逻辑即可。

java 复制代码
@RestControllerAdvice // <--- 包含了 @ResponseBody
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    // 不需要加 @ResponseBody,自动返回 JSON
    public CommonResult<String> handleUserNotFound(UserNotFoundException e) {
        return CommonResult.error(404, e.getMessage());
    }
}

8.3. 联系

继承关系(元注解)
@RestControllerAdvice 是一个元注解(Meta-Annotation) 。如果你查看它的源码,你会发现它的头上标注了 @ControllerAdvice

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice  // <--- 看这里
@ResponseBody      // <--- 看这里
public @interface RestControllerAdvice {
    // ...
}

9. 代码示例

java 复制代码
@RestControllerAdvice
@AllArgsConstructor
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 忽略的 ServiceException 错误提示,避免打印过多 logger
     */
    public static final Set<String> IGNORE_ERROR_MESSAGES = SetUtils.asSet("无效的刷新令牌");

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    private final String applicationName;

    private final ApiErrorLogCommonApi apiErrorLogApi;

    /**
     * 处理所有异常,主要是提供给 Filter 使用
     * 因为 Filter 不走 SpringMVC 的流程,但是我们又需要兜底处理异常,所以这里提供一个全量的异常处理过程,保持逻辑统一。
     *
     * @param request 请求
     * @param ex 异常
     * @return 通用返回
     */
    public CommonResult<?> allExceptionHandler(HttpServletRequest request, Throwable ex) {
        if (ex instanceof MissingServletRequestParameterException) {
            return missingServletRequestParameterExceptionHandler((MissingServletRequestParameterException) ex);
        }
        if (ex instanceof MethodArgumentTypeMismatchException) {
            return methodArgumentTypeMismatchExceptionHandler((MethodArgumentTypeMismatchException) ex);
        }
        if (ex instanceof MethodArgumentNotValidException) {
            return methodArgumentNotValidExceptionExceptionHandler((MethodArgumentNotValidException) ex);
        }
        if (ex instanceof BindException) {
            return bindExceptionHandler((BindException) ex);
        }
        if (ex instanceof ConstraintViolationException) {
            return constraintViolationExceptionHandler((ConstraintViolationException) ex);
        }
        if (ex instanceof ValidationException) {
            return validationException((ValidationException) ex);
        }
        if (ex instanceof MaxUploadSizeExceededException) {
            return maxUploadSizeExceededExceptionHandler((MaxUploadSizeExceededException) ex);
        }
        if (ex instanceof NoHandlerFoundException) {
            return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex);
        }
        if (ex instanceof HttpRequestMethodNotSupportedException) {
            return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
        }
        if (ex instanceof HttpMediaTypeNotSupportedException) {
            return httpMediaTypeNotSupportedExceptionHandler((HttpMediaTypeNotSupportedException) ex);
        }
        if (ex instanceof ServiceException) {
            return serviceExceptionHandler((ServiceException) ex);
        }
        if (ex instanceof AccessDeniedException) {
            return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);
        }
        return defaultExceptionHandler(request, ex);
    }

    /**
     * 处理 SpringMVC 请求参数缺失
     *
     * 例如说,接口上设置了 @RequestParam("xx") 参数,结果并未传递 xx 参数
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public CommonResult<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
        log.warn("[missingServletRequestParameterExceptionHandler]", ex);
        return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()));
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     *
     * 例如说,接口上设置了 @RequestParam("xx") 参数为 Integer,结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public CommonResult<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
        log.warn("[methodArgumentTypeMismatchExceptionHandler]", ex);
        return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()));
    }

    /**
     * 处理 SpringMVC 参数校验不正确
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public CommonResult<?> methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {
        log.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);
        // 获取 errorMessage
        String errorMessage = null;
        FieldError fieldError = ex.getBindingResult().getFieldError();
        if (fieldError == null) {
            // 组合校验,参考自 https://t.zsxq.com/3HVTx
            List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
            if (CollUtil.isNotEmpty(allErrors)) {
                errorMessage = allErrors.get(0).getDefaultMessage();
            }
        } else {
            errorMessage = fieldError.getDefaultMessage();
        }
        // 转换 CommonResult
        if (StrUtil.isEmpty(errorMessage)) {
            return CommonResult.error(BAD_REQUEST);
        }
        return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", errorMessage));
    }

    /**
     * 处理 SpringMVC 参数绑定不正确,本质上也是通过 Validator 校验
     */
    @ExceptionHandler(BindException.class)
    public CommonResult<?> bindExceptionHandler(BindException ex) {
        log.warn("[handleBindException]", ex);
        FieldError fieldError = ex.getFieldError();
        assert fieldError != null; // 断言,避免告警
        return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     *
     * 例如说,接口上设置了 @RequestBody 实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @SuppressWarnings("PatternVariableCanBeUsed")
    public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) {
        log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex);
        if (ex.getCause() instanceof InvalidFormatException) {
            InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
            return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue()));
        }
        if (StrUtil.startWith(ex.getMessage(), "Required request body is missing")) {
            return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误: request body 缺失");
        }
        return defaultExceptionHandler(ServletUtils.getRequest(), ex);
    }

    /**
     * 处理 Validator 校验不通过产生的异常
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public CommonResult<?> constraintViolationExceptionHandler(ConstraintViolationException ex) {
        log.warn("[constraintViolationExceptionHandler]", ex);
        ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
        return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", constraintViolation.getMessage()));
    }

    /**
     * 处理 Dubbo Consumer 本地参数校验时,抛出的 ValidationException 异常
     */
    @ExceptionHandler(value = ValidationException.class)
    public CommonResult<?> validationException(ValidationException ex) {
        log.warn("[constraintViolationExceptionHandler]", ex);
        // 无法拼接明细的错误信息,因为 Dubbo Consumer 抛出 ValidationException 异常时,是直接的字符串信息,且人类不可读
        return CommonResult.error(BAD_REQUEST);
    }

    /**
     * 处理上传文件过大异常
     */
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public CommonResult<?> maxUploadSizeExceededExceptionHandler(MaxUploadSizeExceededException ex) {
        return CommonResult.error(BAD_REQUEST.getCode(), "上传文件过大,请调整后重试");
    }

    /**
     * 处理 SpringMVC 请求地址不存在
     *
     * 注意,它需要设置如下两个配置项:
     * 1. spring.mvc.throw-exception-if-no-handler-found 为 true
     * 2. spring.mvc.static-path-pattern 为 /statics/**
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public CommonResult<?> noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
        log.warn("[noHandlerFoundExceptionHandler]", ex);
        return CommonResult.error(NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getRequestURL()));
    }

    /**
     * 处理 SpringMVC 请求方法不正确
     *
     * 例如说,A 接口的方法为 GET 方式,结果请求方法为 POST 方式,导致不匹配
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public CommonResult<?> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
        log.warn("[httpRequestMethodNotSupportedExceptionHandler]", ex);
        return CommonResult.error(METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage()));
    }

    /**
     * 处理 SpringMVC 请求的 Content-Type 不正确
     *
     * 例如说,A 接口的 Content-Type 为 application/json,结果请求的 Content-Type 为 application/octet-stream,导致不匹配
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public CommonResult<?> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException ex) {
        log.warn("[httpMediaTypeNotSupportedExceptionHandler]", ex);
        return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求类型不正确:%s", ex.getMessage()));
    }

    /**
     * 处理 Spring Security 权限不足的异常
     *
     * 来源是,使用 @PreAuthorize 注解,AOP 进行权限拦截
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    public CommonResult<?> accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) {
        log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", WebFrameworkUtils.getLoginUserId(req),
                req.getRequestURL(), ex);
        return CommonResult.error(FORBIDDEN);
    }

    /**
     * 处理 Guava UncheckedExecutionException
     *
     * 例如说,缓存加载报错,可见 <a href="https://t.zsxq.com/UszdH">https://t.zsxq.com/UszdH</a>
     */
    @ExceptionHandler(value = UncheckedExecutionException.class)
    public CommonResult<?> uncheckedExecutionExceptionHandler(HttpServletRequest req, UncheckedExecutionException ex) {
        return allExceptionHandler(req, ex.getCause());
    }

    /**
     * 处理业务异常 ServiceException
     *
     * 例如说,商品库存不足,用户手机号已存在。
     */
    @ExceptionHandler(value = ServiceException.class)
    public CommonResult<?> serviceExceptionHandler(ServiceException ex) {
        // 不包含的时候,才进行打印,避免 ex 堆栈过多
        if (!IGNORE_ERROR_MESSAGES.contains(ex.getMessage())) {
            // 即使打印,也只打印第一层 StackTraceElement,并且使用 warn 在控制台输出,更容易看到
            try {
                StackTraceElement[] stackTraces = ex.getStackTrace();
                for (StackTraceElement stackTrace : stackTraces) {
                    if (ObjUtil.notEqual(stackTrace.getClassName(), ServiceExceptionUtil.class.getName())) {
                        log.warn("[serviceExceptionHandler]\n\t{}", stackTrace);
                        break;
                    }
                }
            } catch (Exception ignored) {
                // 忽略日志,避免影响主流程
            }
        }
        return CommonResult.error(ex.getCode(), ex.getMessage());
    }

    /**
     * 处理系统异常,兜底处理所有的一切
     */
    @ExceptionHandler(value = Exception.class)
    public CommonResult<?> defaultExceptionHandler(HttpServletRequest req, Throwable ex) {
        // 特殊:如果是 ServiceException 的异常,则直接返回
        // 例如说:https://gitee.com/zhijiantianya/yudao-cloud/issues/ICSSRM、https://gitee.com/zhijiantianya/yudao-cloud/issues/ICT6FM
        if (ex.getCause() != null && ex.getCause() instanceof ServiceException) {
            return serviceExceptionHandler((ServiceException) ex.getCause());
        }

        // 情况一:处理表不存在的异常
        CommonResult<?> tableNotExistsResult = handleTableNotExists(ex);
        if (tableNotExistsResult != null) {
            return tableNotExistsResult;
        }

        // 情况二:处理异常
        log.error("[defaultExceptionHandler]", ex);
        // 插入异常日志
        createExceptionLog(req, ex);
        // 返回 ERROR CommonResult
        return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
    }

    private void createExceptionLog(HttpServletRequest req, Throwable e) {
        // 插入错误日志
        ApiErrorLogCreateReqDTO errorLog = new ApiErrorLogCreateReqDTO();
        try {
            // 初始化 errorLog
            buildExceptionLog(errorLog, req, e);
            // 执行插入 errorLog
            apiErrorLogApi.createApiErrorLogAsync(errorLog);
        } catch (Throwable th) {
            log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(),  JsonUtils.toJsonString(errorLog), th);
        }
    }

    private void buildExceptionLog(ApiErrorLogCreateReqDTO errorLog, HttpServletRequest request, Throwable e) {
        // 处理用户信息
        errorLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
        errorLog.setUserType(WebFrameworkUtils.getLoginUserType(request));
        // 设置异常字段
        errorLog.setExceptionName(e.getClass().getName());
        errorLog.setExceptionMessage(ExceptionUtil.getMessage(e));
        errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
        errorLog.setExceptionStackTrace(ExceptionUtil.stacktraceToString(e));
        StackTraceElement[] stackTraceElements = e.getStackTrace();
        Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
        StackTraceElement stackTraceElement = stackTraceElements[0];
        errorLog.setExceptionClassName(stackTraceElement.getClassName());
        errorLog.setExceptionFileName(stackTraceElement.getFileName());
        errorLog.setExceptionMethodName(stackTraceElement.getMethodName());
        errorLog.setExceptionLineNumber(stackTraceElement.getLineNumber());
        // 设置其它字段
        errorLog.setTraceId(TracerUtils.getTraceId());
        errorLog.setApplicationName(applicationName);
        errorLog.setRequestUrl(request.getRequestURI());
        Map<String, Object> requestParams = MapUtil.<String, Object>builder()
                .put("query", ServletUtils.getParamMap(request))
                .put("body", ServletUtils.getBody(request)).build();
        errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));
        errorLog.setRequestMethod(request.getMethod());
        errorLog.setUserAgent(ServletUtils.getUserAgent(request));
        errorLog.setUserIp(ServletUtils.getClientIP(request));
        errorLog.setExceptionTime(LocalDateTime.now());
    }

    /**
     * 处理 Table 不存在的异常情况
     *
     * @param ex 异常
     * @return 如果是 Table 不存在的异常,则返回对应的 CommonResult
     */
    private CommonResult<?> handleTableNotExists(Throwable ex) {
        String message = ExceptionUtil.getRootCauseMessage(ex);
        if (!message.contains("doesn't exist")) {
            return null;
        }
        if (message.contains("report_")) {
            log.error("[报表模块 spring-module-report - 表结构未导入]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[报表模块 spring-module-report - 表结构未导入]");
        }
        return null;
    }

}
相关推荐
木辰風4 小时前
PLSQL自定义自动替换(AutoReplace)
java·数据库·sql
heartbeat..4 小时前
Redis 中的锁:核心实现、类型与最佳实践
java·数据库·redis·缓存·并发
5 小时前
java关于内部类
java·开发语言
好好沉淀5 小时前
Java 项目中的 .idea 与 target 文件夹
java·开发语言·intellij-idea
gusijin5 小时前
解决idea启动报错java: OutOfMemoryError: insufficient memory
java·ide·intellij-idea
To Be Clean Coder5 小时前
【Spring源码】createBean如何寻找构造器(二)——单参数构造器的场景
java·后端·spring
吨~吨~吨~5 小时前
解决 IntelliJ IDEA 运行时“命令行过长”问题:使用 JAR
java·ide·intellij-idea
你才是臭弟弟5 小时前
SpringBoot 集成MinIo(根据上传文件.后缀自动归类)
java·spring boot·后端
短剑重铸之日5 小时前
《设计模式》第二篇:单例模式
java·单例模式·设计模式·懒汉式·恶汉式
码农水水5 小时前
得物Java面试被问:消息队列的死信队列和重试机制
java·开发语言·jvm·数据结构·机器学习·面试·职场和发展