Spring Boot 全局异常处理最佳实践
在日常开发中,异常处理几乎是绕不过去的一个话题。尤其在 后端 API 项目 中,如果没有统一的异常处理机制,很容易出现以下问题:
- Controller 层代码里充斥着
try-catch
,显得冗余。 - 前端拿到的错误响应格式不一致,增加解析成本。
- 系统异常与业务异常混杂,难以追踪和排查。
因此,在 Spring Boot 项目中,我们通常会通过 全局异常处理 来收敛所有错误,保证接口返回结构的统一性,并简化开发。
一、为什么需要全局异常处理?
在没有全局异常处理之前,开发者常常这样写代码:
@GetMapping("/{id}")
public User getUser(@PathVariable int id) {try {return userService.findById(id);} catch (Exception e) {e.printStackTrace();return null;}
}
问题在于:
try-catch
逻辑冗余,重复代码太多。- 一旦抛出异常,返回值不明确,前端无法准确感知错误信息。
👉 解决方案就是使用 Spring Boot 提供的全局异常处理机制:@ControllerAdvice + @ExceptionHandler
。
二、定义统一返回结果
首先定义一个通用的响应对象 ApiResponse
,用于统一接口返回格式:
public class ApiResponse<T> {private int code;private String message;private T data;public ApiResponse(int code, String message, T data) {this.code = code;this.message = message;this.data = data;}public static <T> ApiResponse<T> success(T data) {return new ApiResponse<>(200, "success", data);}public static <T> ApiResponse<T> error(int code, String message) {return new ApiResponse<>(code, message, null);}// getter & setter
}
这样一来,无论成功还是失败,都能保证返回结果的结构一致。
三、自定义业务异常
除了系统异常(NullPointerException
、SQLException
等),我们还需要定义 业务异常,用于明确业务逻辑错误:
public class BusinessException extends RuntimeException {private int code;public BusinessException(int code, String message) {super(message);this.code = code;}public int getCode() {return code;}
}
例如:用户不存在、余额不足、参数非法等,都可以通过 BusinessException
来抛出。
四、编写全局异常处理器
接下来,通过 @RestControllerAdvice
来统一捕获异常:
@RestControllerAdvice
public class GlobalExceptionHandler {// 处理业务异常@ExceptionHandler(BusinessException.class)public ApiResponse<?> handleBusinessException(BusinessException ex) {return ApiResponse.error(ex.getCode(), ex.getMessage());}// 处理参数校验异常@ExceptionHandler(MethodArgumentNotValidException.class)public ApiResponse<?> handleValidException(MethodArgumentNotValidException ex) {String msg = ex.getBindingResult().getFieldError().getDefaultMessage();return ApiResponse.error(400, msg);}// 兜底异常处理@ExceptionHandler(Exception.class)public ApiResponse<?> handleException(Exception ex) {ex.printStackTrace(); // 可接入日志系统return ApiResponse.error(500, "服务器内部错误");}
}
这样,不管是业务异常还是系统异常,都会走到全局处理器,保证返回结果的统一性。
五、使用示例
Controller
@RestController
@RequestMapping("/user")
public class UserController {@GetMapping("/{id}")public ApiResponse<String> getUser(@PathVariable int id) {if (id == 0) {throw new BusinessException(404, "用户不存在");}return ApiResponse.success("用户ID: " + id);}
}
请求与响应
- 正常请求:
{"code": 200,"message": "success","data": "用户ID: 1"
}
- 业务异常:
{"code": 404,"message": "用户不存在","data": null
}
- 系统异常:
{"code": 500,"message": "服务器内部错误","data": null
}
六、总结
通过 全局异常处理,我们实现了以下目标:
- 统一返回结构,方便前端解析。
- 集中管理异常,减少冗余
try-catch
。 - 区分业务异常与系统异常,提升代码可维护性。
- 可扩展性强,后续可以接入日志系统(如 Logback、ELK)或异常监控平台(如 Sentry)。
建议在实际项目中,将 全局异常处理 作为基础框架的一部分,避免每个 Controller 重复造轮子。
七、日志落库 / ELK 接入最佳实践
在真实的生产环境中,仅仅返回统一的错误信息还不够。我们还需要对异常进行持久化存储与分析,以便后续排查问题和改进系统。
常见的做法有两种:
1. 日志落库(数据库存储)
在全局异常处理器中,可以将异常信息写入数据库(例如 MySQL、PostgreSQL):
@ExceptionHandler(Exception.class)
public ApiResponse<?> handleException(Exception ex, HttpServletRequest request) {// 记录日志信息ErrorLog log = new ErrorLog();log.setUri(request.getRequestURI());log.setMethod(request.getMethod());log.setMessage(ex.getMessage());log.setStackTrace(Arrays.toString(ex.getStackTrace()));log.setCreateTime(LocalDateTime.now());errorLogRepository.save(log); // JPA 或 MyBatis 保存return ApiResponse.error(500, "服务器内部错误");
}
其中 ErrorLog
可以定义为:
@Entity
public class ErrorLog {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String uri;private String method;private String message;private String stackTrace;private LocalDateTime createTime;// getter & setter
}
这样就能将异常详细信息落到数据库,方便后续查询与统计。
2. 接入 ELK(Elasticsearch + Logstash + Kibana)
如果系统日志量较大,推荐接入 ELK,实现实时日志收集与可视化。
(1)配置 Logback 输出 JSON 日志
在 logback-spring.xml
中使用 logstash-logback-encoder
输出 JSON:
<configuration><appender name="LOGSTASH" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>logs/app-log.json</file><encoder class="net.logstash.logback.encoder.LoggingEventEncoder"/></appender><root level="INFO"><appender-ref ref="LOGSTASH"/></root>
</configuration>
(2)通过 Logstash 收集日志
配置 Logstash(logstash.conf):
input {file {path => "/usr/local/app/logs/app-log.json"codec => json}
}output {elasticsearch {hosts => ["http://localhost:9200"]index => "springboot-error-%{+YYYY.MM.dd}"}
}
(3)在 Kibana 可视化
通过 Kibana Dashboard,可以实现:
- 错误趋势图
- 按 API 维度统计异常数量
- 按时间维度分析错误高峰
- 搜索具体异常堆栈
3. 最佳实践建议
- 开发环境:控制台打印异常即可,方便调试。
- 测试环境:日志落库,方便 QA 排查问题。
- 生产环境:接入 ELK,实时收集和可视化,必要时配合 告警系统(如飞书/钉钉机器人、Prometheus Alertmanager)。
八、飞书机器人告警接入
在生产环境中,仅仅记录日志还不够。当发生严重异常时,我们希望能 实时收到告警通知,避免问题被埋没。常见的方式就是接入 企业 IM 工具(如飞书、钉钉、企业微信)。
下面以 飞书自定义机器人 为例,展示如何在全局异常处理器中推送告警。
1. 创建飞书自定义机器人
- 打开飞书群聊 → 设置 → 群机器人 → 添加机器人 → 选择 自定义机器人。
- 复制生成的 Webhook 地址,类似:
https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx
2. 编写工具类(推送异常消息)
使用 RestTemplate
发送 POST 请求:
@Component
public class FeishuBotNotifier {private static final String WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxx";private final RestTemplate restTemplate = new RestTemplate();public void sendAlert(String title, String content) {Map<String, Object> body = new HashMap<>();body.put("msg_type", "text");Map<String, String> text = new HashMap<>();text.put("text", String.format("【异常告警】\n标题: %s\n详情: %s", title, content));body.put("content", text);restTemplate.postForEntity(WEBHOOK_URL, body, String.class);}
}
3. 在全局异常处理器中调用
当捕获到异常时,推送到飞书:
@RestControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionHandler {private final FeishuBotNotifier feishuBotNotifier;@ExceptionHandler(Exception.class)public ApiResponse<?> handleException(Exception ex, HttpServletRequest request) {// 构造告警信息String title = "SpringBoot 服务异常";String content = String.format("URI: %s\nMethod: %s\n错误: %s",request.getRequestURI(), request.getMethod(), ex.getMessage());// 发送飞书告警feishuBotNotifier.sendAlert(title, content);ex.printStackTrace();return ApiResponse.error(500, "服务器内部错误");}
}
4. 飞书群内效果
触发异常后,群内会收到类似消息:
【异常告警】
标题: SpringBoot 服务异常
详情:
URI: /user/0
Method: GET
错误: 用户不存在
九、总结(终极版 🚀)
到这里,我们的 全局异常管理方案 已经形成了一整套闭环:
- 全局异常处理:统一返回格式,简化开发。
- 业务异常区分:明确业务错误与系统错误。
- 日志落库:异常可持久化追溯。
- ELK 接入:日志可视化分析,支持查询与报表。
- 飞书机器人告警:重大异常实时通知,提高运维响应速度。
👉 这样一套机制,基本涵盖了 从接口开发 → 异常收敛 → 日志分析 → 实时告警 的完整链路,既保证了系统的可维护性,也提升了线上运维的响应效率。