异常处理实践
异常处理是系统健壮性的保障,全局异常管理让代码更清晰、问题可追溯。
1. 异常分类
1.1 异常层次结构
Throwable
├── Error(系统级错误,不应该捕获)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── ...
├── Exception
│ ├── RuntimeException( unchecked,不强制声明)
│ │ ├── BusinessException ← 业务异常,可捕获处理
│ │ ├── IllegalArgumentException
│ │ └── ...
│ └── IOException / SQLException( checked,强制声明)
└ └── 业务异常基类
1.2 项目中的异常定义
// 业务异常(用户可感知)
public class BusinessException extends RuntimeException {
private String code;
private String message;
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
}
// 系统异常(需要记录日志)
public class SystemException extends RuntimeException {
private String code;
public SystemException(String code, String message) {
super(message);
this.code = code;
}
}
2. 全局异常处理
2.1 Spring Boot 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
// 业务异常
@ExceptionHandler(BusinessException.class)
public Result handleBusinessException(BusinessException e) {
log.warn("业务异常: code={}, message={}", e.getCode(), e.getMessage());
return Result.error(e.getCode(), e.getMessage());
}
// 参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
log.warn("参数校验异常: {}", message);
return Result.error(400, message);
}
// 断言异常
@ExceptionHandler(IllegalArgumentException.class)
public Result handleIllegalArgumentException(IllegalArgumentException e) {
log.warn("参数异常: {}", e.getMessage());
return Result.error(400, e.getMessage());
}
// 系统异常
@ExceptionHandler(SystemException.class)
public Result handleSystemException(SystemException e) {
log.error("系统异常: code={}, message={}", e.getCode(), e.getMessage(), e);
return Result.error(500, "系统繁忙,请稍后重试");
}
// 空指针异常
@ExceptionHandler(NullPointerException.class)
public Result handleNullPointerException(NullPointerException e) {
log.error("空指针异常", e);
return Result.error(500, "系统繁忙,请稍后重试");
}
// 未知异常(兜底)
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
log.error("未知异常", e);
return Result.error(500, "系统繁忙,请稍后重试");
}
}
2.2 统一响应结构
public class Result<T> {
private int code;
private String message;
private T data;
private long timestamp;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
result.setTimestamp(System.currentTimeMillis());
return result;
}
public static <T> Result<T> error(int code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
result.setData(null);
result.setTimestamp(System.currentTimeMillis());
return result;
}
public static <T> Result<T> error(String code, String message) {
Result<T> result = new Result<>();
result.setCode(-1);
result.setMessage(message);
result.setData(null);
result.setTimestamp(System.currentTimeMillis());
return result;
}
}
3. 异常页面处理
3.1 HTML 错误页面
// 在全局异常处理中,返回 JSON 或 HTML
@ExceptionHandler(NoHandlerFoundException.class)
public Result handleNotFoundException(NoHandlerFoundException e) {
// 判断是否 AJAX 请求
if (isAjaxRequest()) {
return Result.error(404, "接口不存在");
}
// 返回 HTML 页面(可配置 error/404.html)
return Result.error(404, "页面不存在");
}
3.2 错误页面配置
# application.yml
server:
error:
path: /error
include-message: always
include-binding-errors: always
include-stacktrace: on_param
include-exception: true
4. 异常日志规范
4.1 日志记录原则
| 异常类型 | 日志级别 | 记录内容 |
|---|---|---|
| 业务异常 | WARN | code + message |
| 参数异常 | WARN | 参数校验信息 |
| 系统异常 | ERROR | 完整堆栈信息 |
| 未知异常 | ERROR | 完整堆栈信息 |
4.2 日志脱敏
@Aspect
@Component
public class ExceptionLogAspect {
@Around("execution(* com.example..*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
return joinPoint.proceed();
} catch (Exception e) {
// 脱敏后记录日志
String params = sanitizeParams(joinPoint.getArgs());
log.error("方法: {}, 参数: {}, 异常: {}",
joinPoint.getSignature(),
params,
e.getMessage());
throw e;
}
}
private String sanitizeParams(Object[] args) {
// 脱敏手机号、身份证、密码等敏感信息
return JSON.toJSONString(args);
}
}
5. 自定义业务异常
5.1 错误码枚举
public enum ErrorCode {
// 通用错误 1xxx
PARAM_INVALID(1001, "参数错误"),
DATA_NOT_FOUND(1002, "数据不存在"),
DATA_DUPLICATE(1003, "数据重复"),
// 业务错误 2xxx
USER_NOT_FOUND(2001, "用户不存在"),
USER_DISABLED(2002, "用户已被禁用"),
PASSWORD_ERROR(2003, "密码错误"),
// 系统错误 9xxx
SYSTEM_ERROR(9001, "系统错误"),
SERVICE_UNAVAILABLE(9002, "服务不可用");
private final int code;
private final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
}
5.2 业务异常工厂
public class BizException {
public static BusinessException fail(ErrorCode errorCode) {
return new BusinessException(String.valueOf(errorCode.getCode()), errorCode.getMessage());
}
public static BusinessException fail(ErrorCode errorCode, String message) {
return new BusinessException(String.valueOf(errorCode.getCode()), message);
}
public static BusinessException fail(int code, String message) {
return new BusinessException(String.valueOf(code), message);
}
}
6. 生产环境异常处理
6.1 异常隔离
// 不同模块使用不同的异常处理器
@RestControllerAdvice(basePackages = {"com.example.user"})
public class UserExceptionHandler {
// 处理用户模块异常
}
@RestControllerAdvice(basePackages = {"com.example.order"})
public class OrderExceptionHandler {
// 处理订单模块异常
}
6.2 异常告警
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
// 发送告警(钉钉/企微/短信)
if (isCriticalException(e)) {
alertService.sendAlert("系统异常", e.getMessage());
}
return Result.error(500, "系统繁忙,请稍后重试");
}
private boolean isCriticalException(Exception e) {
return e instanceof SystemException
|| e instanceof NullPointerException
|| e instanceof OutOfMemoryError;
}
6.3 异常链路追踪
@ExceptionHandler(Exception.class)
public Result handleException(Exception e, HttpServletRequest request) {
// 生成异常唯一ID,便于追踪
String traceId = UUID.randomUUID().toString().replace("-", "");
log.error("异常ID: {}, 请求: {}, 异常: {}",
traceId,
request.getRequestURI(),
e);
// 返回异常ID给前端,便于反馈问题
return Result.error(500, "系统繁忙,请稍后重试")
.setTraceId(traceId);
}
7. 注意事项
- 不要捕获所有异常:Error 应该让 JVM 处理
- 不要只 throw 不 catch:业务异常要有明确的处理
- 异常要包含上下文:便于排查问题
- 生产环境不暴露堆栈:对外只返回 message
- 异常要有错误码:便于前端判断和处理
最后更新:2026/05/13