1. 前言
在 Spring Boot 开发中,我们习惯使用 @RestControllerAdvice 和 @ExceptionHandler 来进行全局异常处理。但你是否思考过以下问题:
- 当 Controller 抛出异常时,是谁捕获了它?
- 为什么我们在类上加个注解@RestControllerAdvice,Spring 就能自动调用我们的方法?
GlobalExceptionHandler到底是被谁调用的?- 为什么 Filter 中的异常默认无法被捕获?
本文将剥离表象,深入 DispatcherServlet 和 HandlerExceptionResolver 源码,还原一条清晰的异常处理逻辑链条。
2. 核心组件全景图
在深入代码之前,我们先理清几个核心角色的关系:
DispatcherServlet: 前端控制器。它是异常处理的发起者,负责捕获 Controller 抛出的异常,并委托给解析器处理。HandlerExceptionResolver: 异常解析器接口。定义了"如何处理异常"的标准。ExceptionHandlerExceptionResolver: 接口的核心实现类 。它专门负责处理@ExceptionHandler注解。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 会默认自动配置以下实现类(按优先级排序):
ExceptionHandlerExceptionResolver:处理@ExceptionHandler(最重要)。ResponseStatusExceptionResolver:处理@ResponseStatus。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
让我们把所有环节串联起来,看一个完整的执行流:
- 抛出 :Controller 执行业务逻辑,
throw new ServiceException(404, "用户未找到")。 - 捕获 :
DispatcherServlet的try-catch块捕获该异常。 - 分发 :
DispatcherServlet遍历 Resolvers,调用resolveException。 - 定位 :
ExceptionHandlerExceptionResolver介入,通过异常类型ServiceException查找缓存,定位到GlobalExceptionHandler.serviceExceptionHandler方法。 - 调用 :通过反射执行该方法,得到返回值
CommonResult对象。 - 转换 :
RequestResponseBodyMethodProcessor调用 Jackson 的ObjectMapper,将CommonResult序列化为 JSON 字符串。 - 写入 :JSON 字符串被写入
HttpServletResponse的输出流。 - 结束 :
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;
}
}