Spring Boot 全局异常处理器(GlobalExceptionHandler) 的核心代码,是和 CommonResult<T> 配套的后端统一异常处理方案,每一行代码的作用 ,以及为什么要单独写这个类 。
This is the core code of the Spring Boot GlobalExceptionHandler, which is part of the "backend unified exception handling" solution paired with CommonResult<T> . Below, I will explain the function of each line of code, as well as why this class is written separately.
一、代码逐行拆解
1. 核心注解与类结构
// 全局异常处理器注解,标记这是一个全局异常处理类
@RestControllerAdvice
public class GlobalExceptionHandler {
// 注入日志对象,用于打印异常栈
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// 1. 处理 Controller 层自定义异常(示例)
@ExceptionHandler(value = ControllerException.class)
public CommonResult<?> controllerException(ControllerException e) {
// 打印错误日志,方便排查问题
logger.error("controllerException:", e);
// 封装统一错误响应,返回给前端
return CommonResult.error(
GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),
e.getMessage()
);
}
// 2. 处理所有未捕获的 Exception 异常(兜底)
@ExceptionHandler(value = Exception.class)
public CommonResult<?> exception(Exception e) {
// 打印服务异常日志,包含完整异常栈
logger.error("服务异常:", e);
// 封装统一错误响应,返回给前端
return CommonResult.error(
GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),
e.getMessage()
);
}
}
2. 关键注解说明
| 注解 | 作用 |
|---|---|
@RestControllerAdvice |
标记类为全局异常处理器 ,会拦截所有 @RestController 抛出的未捕获异常,是全局异常处理的核心注解 |
@ExceptionHandler(value = 异常类.class) |
标记方法用于处理指定类型的异常,value = Exception.class 表示捕获所有未被其他方法处理的异常(兜底) |
3. 核心逻辑拆解
-
日志打印 :
logger.error("xxx:", e)- 作用:把完整的异常栈信息打印到日志文件中,方便开发 / 运维排查线上问题
- 注意:这里必须传
e(异常对象),才能打印完整的异常栈,只传e.getMessage()只会打印错误信息,无法定位问题
-
统一响应封装 :
CommonResult.error(...)- 作用:把异常封装成和
CommonResult<T>完全一致的格式返回给前端 - 结构:
{ "code": 500, "msg": "xxx错误信息", "data": null } - 优势:前端不用再处理「接口抛异常返回 500 错误页」的情况,永远收到统一格式的响应
- 作用:把异常封装成和
-
错误码常量 :
GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR- 作用:统一管理错误码,避免魔法值(比如直接写 500),保证全项目错误码一致
- 示例:
INTERNAL_SERVER_ERROR对应code=500,msg="服务器内部错误"
二、为什么要单独写这个类?(核心设计意义)
如果不写这个全局异常处理器,会出现什么问题?
1. 原生 Spring Boot 异常返回的痛点
- 接口失败时,前端收到的是「错误页 / 500 状态码」,不是统一格式的 JSON 比如:
Whitelabel Error Page页面,前端无法解析,直接报错 - 不同异常的返回结构五花八门,前端要写 N 套错误处理逻辑
- 异常信息直接暴露给用户 :比如
NullPointerException、SQL 语法错误等敏感信息,有安全风险 - 业务异常和系统异常混在一起,排查困难
2. 全局异常处理器的核心价值
✅ 1. 统一异常返回格式,和 CommonResult 完美适配
所有异常(不管是业务异常、系统异常)都被封装成 CommonResult.error(),前端永远收到统一格式的 JSON,一套逻辑处理所有错误,彻底解决前后端联调的痛点。
✅ 2. 业务代码零侵入,专注业务逻辑
Controller/Service 层只需要直接抛异常 ,不用手动写 try-catch 封装 CommonResult.error(),业务代码更干净、更易维护。
// 业务代码中,直接抛异常即可,不用手动处理返回
if (user == null) {
throw new BusinessException("用户不存在");
}
✅ 3. 敏感信息隐藏,提升系统安全性
可以自定义异常处理逻辑,不把底层异常栈(如 SQL 错误、空指针)直接返回给用户,只返回友好的提示信息,避免泄露系统细节。
// 优化示例:隐藏底层异常,返回统一提示
return CommonResult.error(
GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),
"服务器繁忙,请稍后再试"
);
✅ 4. 统一日志管理,便于问题排查
所有异常都在这个类中统一打印日志,不用在每个 Controller 中手动写日志,日志格式统一,排查问题更高效。
✅ 5. 分层异常分类处理,可扩展性极强
可以针对不同类型的异常,写不同的处理逻辑:
- 业务异常(
BusinessException):返回业务错误码(如 400 = 参数错误) - 权限异常(
UnauthorizedException):返回 401 未授权 - 系统异常(
Exception):返回 500 服务器错误 - 限流异常、熔断异常等:单独处理,返回对应错误码
三、完整优化版代码(生产环境可直接用)
package com.example.lotterysystem.controller.handler;
import com.example.lotterysystem.common.errorcode.GlobalErrorCodeConstants;
import com.example.lotterysystem.common.exception.BusinessException;
import com.example.lotterysystem.common.pojo.CommonResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
/**
* 全局异常处理器
* 统一处理所有Controller抛出的异常,封装为CommonResult返回
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理业务自定义异常(主动抛出的业务错误)
*/
@ExceptionHandler(value = BusinessException.class)
public CommonResult<?> businessException(BusinessException e) {
logger.error("业务异常: {}", e.getMessage(), e);
return CommonResult.error(e.getCode(), e.getMessage());
}
/**
* 处理参数校验异常(@Valid 注解校验失败)
*/
@ExceptionHandler(value = ConstraintViolationException.class)
public CommonResult<?> constraintViolationException(ConstraintViolationException e) {
logger.error("参数校验异常: {}", e.getMessage(), e);
// 提取第一个校验错误信息
String msg = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.findFirst()
.orElse("参数错误");
return CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), msg);
}
/**
* 处理所有未捕获的系统异常(兜底)
*/
@ExceptionHandler(value = Exception.class)
public CommonResult<?> exception(Exception e) {
logger.error("服务异常: ", e);
// 生产环境隐藏底层异常,返回统一提示,避免泄露敏感信息
return CommonResult.error(
GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),
"服务器繁忙,请稍后再试"
);
}
}
四、和 CommonResult<T> 的联动流程(完整闭环)
前端发起请求
↓
Controller 接收参数
↓
Service 执行业务逻辑
↓
业务执行结果
/ \
成功 失败/抛异常
↓ ↓
CommonResult.success() → 全局异常处理器捕获异常
↓
CommonResult.error()
↓
SpringBoot 序列化为统一JSON
↓
返回给前端统一格式响应
五、关键优化建议(生产环境必做)
-
禁止直接返回
e.getMessage()给前端 生产环境中,系统异常(如空指针、SQL 错误)的getMessage()会泄露系统细节,必须替换为统一提示(如「服务器繁忙,请稍后再试」),只在日志中打印完整异常栈。 -
自定义业务异常类 单独定义
BusinessException继承RuntimeException,用于主动抛出业务错误,和系统异常区分开,方便分类处理。 -
错误码枚举化 所有错误码都定义在
GlobalErrorCodeConstants枚举中,避免魔法值,保证全项目错误码一致。 -
参数校验异常单独处理 针对
@Valid注解的参数校验异常,单独提取错误信息,返回友好提示,提升用户体验。