夏有木工作室夏有木工作室夏有木工作室
  • 首页
  • 博客
  • 关于我们
  • 联系我们
夏有木工作室夏有木工作室夏有木工作室
全部文章/
架构设计

良好的异常处理设计:系统稳定性的基础保障

EricEric
2024/11/27
·4 分钟阅读 (1,139 字)·

探讨企业级后端的全局异常体系设计,规范业务错误码、状态流转异常捕获与微服务防御性兜底保障。

目录

  • 异常的分类
  • 异常错误码设计
  • 异常统一处理
  • 执行顺序:Filter > HandlerInterceptor > Controller
  • 全局异常处理实现
  • 小结
  • 参考文档
  • 架构设计推荐阅读

异常一定会发生,并且异常处理是任何软件系统至关重要的一部分,良好的异常处理设计是系统稳定性的基础保障。

面向异常信息的一般有两类人群,一是用户(系统使用者),二是开发人员,先来展示下几个具体的痛点以及对应的设计思路:

异常处理设计目标和要点

下面重点讲述下如何设计一个优雅的异常处理机制。

异常的分类

将异常进行分类,以便更清晰地表达异常含义,同时保持与前端 HTTP 状态码的良好兼容。

异常分类

  • BaseException :所有自定义异常的基类,便于统一管理和扩展。
  • BizException :表示业务逻辑相关的异常,例如:非法参数输入、商品库存不足等
  • SysException : 表示系统运行相关的异常,例如:数据库连接失败、获取锁失败等
  • ClientException :表示外部系统调用失败异常,例如:调用第三方支付接口失败、微服务内接口调用报错等
  • AuthException:表示认证和鉴权相关的异常,例如:Token 过期、无效 Token,用户无权访问资源等

设计要点与扩展性:

  1. HttpStatus 状态码绑定

    每种异常类型预定义一个适用的 HTTP 状态码,方便服务端直接转换为前端可识别的响应格式。例如:401 认证失败错误码时,前端需要进行登出处理。

  2. 错误码设计 使用 errCode 字段标识具体的错误场景,建议遵循一定的编码规则,如:纯字母/模块标识 + 错误编码(如 USER_0001 表示用户模块的用户不存在,或者直接NOT_FOUND 表示数据不存在)。

  3. 统一异常处理

    便于进行统一的异常处理逻辑,例如:Spring Boot 中的 @ControllerAdvice 和 @ExceptionHandler

  4. 扩展性 如果未来有新的异常场景,可以继续继承 BaseException 创建字类。

部分代码实现:

/**
* 异常基类
*/
@Getter
@Setter
public abstract class BaseException extends RuntimeException {
  
  private int httpStatus;
  private String errCode;
  private String errMessage;

  public BaseException(int httpStatus, String errorCode, String errMessage) {
      super(errMessage);
      this.httpStatus = httpStatus;
      this.errCode = errorCode;
      this.errMessage = errMessage;
  }

  public BaseException(int httpStatus, String errorCode, String errMessage, Throwable cause) {
      super(errMessage, cause);
      this.httpStatus = httpStatus;
      this.errCode = errorCode;
      this.errMessage = errMessage;
  }
}
/**
 * 业务异常,例如:400 和 404 错误
 */
public class BizException extends BaseException {
  
  private static final int BIZ_EXCEPTION_HTTP_STATUS = 400;

  private static final String DEFAULT_ERR_CODE = "BIZ_ERROR";

  public BizException(String errCode, String errMessage) {
      super(BIZ_EXCEPTION_HTTP_STATUS, errCode, errMessage);
  }

  public BizException(int httpStatus, String errorCode, String errorMessage) {
      super(httpStatus, errorCode, errorMessage); // 可指定 404 等其他状态
  }

  public BizException(String errorCode, String errorMessage, Throwable cause) {
      super(BIZ_EXCEPTION_HTTP_STATUS, errorCode, errorMessage, cause);
  }

  public BizException(String errorMessage) {
      super(BIZ_EXCEPTION_HTTP_STATUS, DEFAULT_ERR_CODE, errorMessage);
  }
}

异常错误码设计

轻量级错误码设计方案:

  • 纯字母错误码:使用纯字母进行异常错误的描述,比如:TOKEN_INVALID、AUTHORIZE_FORBIDDEN、NOT_FOUND 等

  • 项目编码+ 模块编码 + 错误编码:比如:PDM_USER_001 或者更细的力度。同时需要维护一个错误码和错误描述对应的文档。

  • 阿里巴巴 Java 开发手册错误码:A0100 表示用户注册错误,错误码规则如下

    1. 五位组成
    2. A代表用户端错误
    3. B代表当前系统执行异常
    4. C代表第三方服务异常
    5. 若无法确定具体错误,选择宏观错误
    6. 大的错误类间的步长间距预留100

一些参考实例:

1)谷歌 API 错误码定义(采用的是纯字母错误码)

Google API 错误码定义

2)阿里错误码定义(采用的是错误类型+ 4 位数字错误编码)

阿里错误码定义

异常统一处理

以下是 Java + SpringBoot 框架下的异常统一处理的方案。通常我们在开发 Web 应用的时候,会涉及到三类组件,分别是:Controller、Filter 和 HandlerInterceptor 。

执行顺序:Filter > HandlerInterceptor > Controller

1)Filter

  • 最外层的组件,处理请求的最早阶段。
  • 负责请求的预处理(如认证、日志记录)以及响应的后处理。
  • 对应 Servlet 的过滤器机制。

2)HandlerInterceptor

  • 位于 Spring MVC 的请求分发流程中。
  • 适合对请求进行细粒度的检查(如权限验证、业务逻辑校验)。
  • preHandle 在 Controller 之前执行,postHandle 和 afterCompletion 在 Controller 或异常处理器之后执行。

3)Controller

  • 核心业务逻辑处理。
  • ControllerAdvice 和 @ExceptionHandler 处理 Controller 方法抛出的异常。

全局异常处理实现

首先定义一个 ExceptionResponse 类:

public class Response {

  private boolean success;

  private String errCode;

  private String errMessage;

  private int httpStatus;

	public static Response buildFailure(int httpStatus, String errCode, String errMessage) {
      Response response = new Response();
      response.setHttpStatus(httpStatus);
      response.setSuccess(false);
      response.setErrCode(errCode);
      response.setErrMessage(errMessage);
      return response;
  }
}

然后通过 Exception 或 HttpResponse来构建 Response:

@Slf4j
public class ExceptionResponseUtil {

  public static Response buildResponseFromException(Throwable e) {
      int httpStatus = HttpStatus.INTERNAL_SERVER_ERROR.value();
      String errCode = "UNKNOWN_ERROR";
      String errMessage = e.getMessage();

      if (e instanceof BaseException baseException) {
          httpStatus = baseException.getHttpStatus();
          errCode = baseException.getErrCode();
      }

      log.error("Unified Catch Exception : [{}-{}] {}", httpStatus, errCode, errMessage, e);
      return Response.buildFailure(httpStatus, errCode, errMessage);
  }

  @SneakyThrows
  public static void handleResponseFromHttpRequest(
          HttpServletRequest request,
          HttpServletResponse response,
          Throwable e) {
      Response respEntity = buildResponseFromException(e);
      response.setStatus(respEntity.getHttpStatus());
      ServletUtil.printResponse(request, response, respEntity);
  }

}

最后在 Filter 或者 ControllerAdvice 中统一处理异常:

@Slf4j
@ResponseBody
@ControllerAdvice
public class HttpExceptionAdvice {

  @ExceptionHandler(Throwable.class)
  public ResponseEntity<Object> handleException(Throwable e) {
      Response response = ExceptionResponseUtil.buildResponseFromException(e);
      return new ResponseEntity<>(response, HttpStatus.valueOf(response.getHttpStatus()));
  }

}
@Slf4j
public class HttpFilter implements Filter {
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
      log.info("http filter init");
  }

  @Override
  public void doFilter(ServletRequest servletRequest,
                       ServletResponse servletResponse,
                       FilterChain filterChain) {
      HttpServletRequest request = (HttpServletRequest) servletRequest;
      HttpServletResponse response = (HttpServletResponse) servletResponse;
      try {
          // 执行具体的 filter 逻辑

          filterChain.doFilter(servletRequest, servletResponse);
      } catch (Throwable e) {
          handleResponseFromHttpRequest(request, response, e);
      } finally {
          MDCUtil.clean();
      }
  }

  @Override
  public void destroy() {
      log.info("http filter destroy");
  }
}

小结

通过定义规范的错误码,统一的前端响应体,以及异常日志打印,能够有效的减少代码的复杂度,有利于代码的维护,并且能够快速定位到 Bug,大大提高开发效率。

参考文档

Goolea API 错误码

阿里 Java 开发手册(黄山版)

架构设计推荐阅读

如果你对高并发 API 性能优化、分布式数据一致性、索引治理与系统稳定性感兴趣,推荐阅读:

  • 数据一致性方案设计
  • API 接口性能优化
  • MySQL 创建高性能的索引
  • 异步批处理方案设计:JDK21 + 虚拟线程
  • 分布式追踪:SpringBoot 3.x + MDC
  • 良好的异常处理设计:系统稳定性的基础保障

👉 查阅更多企业级系统设计与工程实战,欢迎访问 架构设计专栏完整目录。

上一篇

微信小程序接入隐私政策

下一篇

微信小程序订阅消息设计

更多文章

使用 Sa-Token 平替 Spring Security,告别繁琐的认证与鉴权

使用 Sa-Token 平替 Spring Security,告别繁琐的认证与鉴权

对比传统 Spring Security 的复杂繁重,分享使用轻量 Sa-Token 框架实现无痛多端鉴权、单点登录与路由拦截的最佳实践。

EricEric
2025/01/15
为什么写不好 DDD 代码?

为什么写不好 DDD 代码?

深度剖析为什么大部分团队写不好 DDD,从建模思维、业务边界侵蚀到防腐层设计,解析领域驱动的核心痛点与破局解法。

EricEric
2025/01/14
MySQL 创建高性能的索引

MySQL 创建高性能的索引

深入 MySQL 索引底层原理,总结 B+ 树特性、最左匹配原则、覆盖索引与排查慢查询的高效索引设计军规。

EricEric
2024/09/26
夏有木工作室夏有木工作室夏有木工作室

专业软件开发技术伙伴,帮助客户快速构建出高质量的软件产品。

GitHubGitHubEmail
Built with夏有木工作室夏有木工作室夏有木

川公网安备51012202002478号|蜀ICP备2025156031号-1

产品
  • 夏有木进销存
工具
  • 夏有木微信公众号排版
资源
  • 博客
  • 更新日志
公司
  • 关于我们
  • 联系我们
法律
  • Cookie政策
  • 隐私政策
  • 服务条款
© 2026 夏有木工作室 All Rights Reserved.