代码报错了怎么办?web报错和rpc报错为什么不能用同一套处理逻辑?
这是单体架构转微服务架构后,90%开发者都会踩的核心大坑。在传统单体项目中,我们只需适配浏览器web接口异常,统一返回json即可;但在微服务架构下,项目同时存在 http web接口(对外给前端) 和 rpc接口(对内服务调用)两套通信模式。
两套接口的异常传播机制、返回规范、兜底逻辑、日志要求完全不同:web接口需要友好前端提示、屏蔽堆栈;rpc接口需要透传错误码、保留异常信息、支持服务降级重试。如果全局异常不做web/rpc差异化处理,会出现服务调用雪崩、错误码丢失、微服务调用异常无法溯源、前端/客户端报错错乱等致命线上问题。
本文基于springboot2.x/3.x全版本,从底层机制差异、双场景异常分类、全景对比、差异化代码实现、踩坑避坑、企业规范全方位解析,搭配多维度标准表格,打造适配单体+微服务的完整全局异常处理体系,彻底解决不同场景代码报错兜底问题。
一、微服务架构下的核心痛点:为什么要区分web与rpc异常?
很多项目全局异常失效、微服务调用报错、服务间异常透传失败,根源均为:用web接口的异常逻辑处理了rpc接口报错。下表直观展示统一处理带来的线上致命问题。
适配场景 | 统一web异常逻辑(错误做法) | 造成的线上问题 |
|---|---|---|
http web接口 | 统一返回友好提示、屏蔽堆栈、统一200状态码 | 正常适配前端,无问题,用户体验良好 |
rpc服务接口 | 统一屏蔽异常堆栈、覆盖原始错误码、吞掉业务异常 | 1. 下游服务只收到“系统异常”,无法精准处理降级;2. 原始业务错误码丢失,服务间校验失效;3. 无堆栈信息,微服务报错无法溯源;4. rpc框架判定无异常,重试机制失效 |
核心结论:web异常重在屏蔽、友好兜底,rpc异常重在透传、精准溯源,二者必须差异化处理,不可共用一套逻辑。
二、底层原理:web vs rpc 异常机制全景对比
web接口和rpc接口的异常拦截链路、执行时机、框架机制完全不同,这是差异化处理的底层根本原因。
2.1 两套接口异常传播链路差异
接口类型 | 核心通信协议 | 异常传播链路 | 拦截依赖 |
|---|---|---|---|
web接口(http) | http/https | service/dao异常 → controller → dispatcherservlet → @restcontrolleradvice → 前端json响应 | spring mvc aop 切面,仅拦截http请求 |
rpc接口(dubbo/feign) | tcp/自定义协议 | service业务异常 → rpc服务骨架 → 框架异常过滤器 → 抛出远程异常 → 消费端捕获 | spring aop + rpc框架自适应切面,不被mvc拦截器捕获 |
2.2 核心特性全方位对比(企业核心依据)
对比维度 | web http 接口异常 | rpc 服务接口异常 |
|---|---|---|
服务对象 | 面向前端浏览器、app客户端 | 面向后端微服务、内部服务调用 |
设计目标 | 用户体验优先、屏蔽底层细节、保障安全 | 数据精准优先、异常透传、保障调用一致性 |
堆栈处理策略 | 生产环境屏蔽堆栈,仅展示友好文案 | 生产环境保留关键堆栈,用于服务溯源 |
错误码策略 | 统一http状态码+业务码,简化前端处理 | 必须透传原始业务错误码,保证消费端可识别 |
日志级别 | 业务异常warn、系统异常error | 所有rpc业务异常均为error,强制记录链路日志 |
重试机制 | 前端自主重试,服务端不处理 | 依赖rpc框架重试、降级、熔断,异常不可吞 |
兜底原则 | 宁可错兜底,不可暴露信息 | 宁可抛异常,不可丢失异常信息 |
三、双场景异常分类与标准化处理策略
结合web和rpc双场景,重新定义项目四类异常的差异化处理规则,覆盖微服务99%报错场景。
异常类型 | 典型异常类 | web接口处理策略 | rpc接口处理策略 |
|---|---|---|---|
自定义业务异常 | businessexception | 返回友好业务提示,warn日志,无堆栈 | 完整透传错误码+提示信息,error日志,保留简要堆栈,供消费端判断降级 |
参数校验异常 | methodargumentnotvalidexception | 返回字段精准错误提示,简化文案 | 抛出标准化参数异常,透传字段错误详情,禁止笼统提示 |
系统运行时异常 | 空指针、类型转换、数组越界 | 屏蔽详情,提示“系统繁忙”,error日志存堆栈 | 直接抛出原始异常,完整堆栈日志,触发rpc熔断降级 |
未知全局异常 | exception父类 | 全局兜底,屏蔽底层信息 | 原样上抛,不兜底掩盖,保证微服务链路可追踪 |
四、生产级全套差异化代码实现(web+rpc双适配)
整套代码解决核心问题:web接口统一友好返回、rpc接口精准透传异常,通过注解区分接口场景,零侵入适配双模式,可直接上线微服务项目。
4.1 基础通用组件(共用无修改)
统一响应体、错误码枚举、自定义业务异常为项目通用组件,双场景共用。
import lombok.data;
/**
* 全局统一响应体
*/
@data
public class result<t> {
private integer code;
private string msg;
private t data;
public static <t> result<t> success(t data) {
result<t> result = new result<>();
result.setcode(200);
result.setmsg("操作成功");
result.setdata(data);
return result;
}
public static <t> result<t> success() {
return success(null);
}
public static <t> result<t> error(integer code, string msg) {
result<t> result = new result<>();
result.setcode(code);
result.setmsg(msg);
return result;
}
}import lombok.allargsconstructor;
import lombok.getter;
/**
* 全局统一错误码
*/
@getter
@allargsconstructor
public enum errorcodeenum {
system_error(500, "系统内部异常,请稍后重试"),
param_error(400, "请求参数非法"),
auth_error(401, "登录认证失败"),
permission_error(403, "权限不足"),
user_not_exist(1001, "用户不存在"),
password_error(1002, "密码错误"),
balance_not_enough(1003, "账户余额不足");
private final integer code;
private final string msg;
}import lombok.data;
/**
* 自定义业务异常(web/rpc通用)
*/
@data
public class businessexception extends runtimeexception {
private integer code;
private string msg;
public businessexception(errorcodeenum errorcode) {
super(errorcode.getmsg());
this.code = errorcode.getcode();
this.msg = errorcode.getmsg();
}
public businessexception(integer code, string msg) {
super(msg);
this.code = code;
this.msg = msg;
}
}4.2 场景标记注解(核心:区分web/rpc接口)
自定义注解标记rpc接口,实现异常处理器动态适配不同场景。
import java.lang.annotation.*;
/**
* 标记rpc服务接口
* 被该注解标记的接口,执行rpc异常透传逻辑
*/
@target({elementtype.type, elementtype.method})
@retention(retentionpolicy.runtime)
@documented
public @interface rpcapi {
}4.3 核心:双场景差异化全局异常处理器
自动识别web接口和rpc接口,执行不同的异常兜底逻辑,完美适配微服务架构。
import lombok.extern.slf4j.slf4j;
import org.springframework.validation.bindexception;
import org.springframework.validation.fielderror;
import org.springframework.web.bind.methodargumentnotvalidexception;
import org.springframework.web.bind.annotation.exceptionhandler;
import org.springframework.web.bind.annotation.restcontrolleradvice;
import org.springframework.web.method.handlermethod;
import javax.servlet.http.httpservletrequest;
/**
* 微服务全局异常处理器
* 差异化处理:web接口友好兜底、rpc接口异常透传
*/
@slf4j
@restcontrolleradvice
public class globalexceptionhandler {
/**
* 判断当前是否为rpc接口请求
*/
private boolean isrpcapi(handlermethod handlermethod) {
if (handlermethod == null) {
return false;
}
// 方法或类标记@rpcapi注解则判定为rpc接口
return handlermethod.hasmethodannotation(rpcapi.class)
|| handlermethod.getbeantype().isannotationpresent(rpcapi.class);
}
// 1、自定义业务异常差异化处理
@exceptionhandler(businessexception.class)
public result<void> businessexceptionhandler(httpservletrequest request, handlermethod handlermethod, businessexception e) {
// rpc接口:error日志、透传详情,不屏蔽信息
if (isrpcapi(handlermethod)) {
log.error("【rpc业务异常】code:{}, msg:{}", e.getcode(), e.getmsg());
throw e;
}
// web接口:warn日志、友好提示
log.warn("【web业务异常】code:{}, msg:{}", e.getcode(), e.getmsg());
return result.error(e.getcode(), e.getmsg());
}
// 2、参数校验异常差异化处理
@exceptionhandler(methodargumentnotvalidexception.class)
public result<void> validexceptionhandler(httpservletrequest request, handlermethod handlermethod, methodargumentnotvalidexception e) {
fielderror fielderror = e.getbindingresult().getfielderror();
string msg = fielderror != null ? fielderror.getdefaultmessage() : "参数非法";
if (isrpcapi(handlermethod)) {
log.error("【rpc参数异常】{}", msg);
throw new businessexception(errorcodeenum.param_error.getcode(), msg);
}
log.warn("【web参数异常】{}", msg);
return result.error(errorcodeenum.param_error.getcode(), msg);
}
// 3、空指针系统异常差异化处理
@exceptionhandler(nullpointerexception.class)
public result<void> nullexceptionhandler(httpservletrequest request, handlermethod handlermethod, nullpointerexception e) {
if (isrpcapi(handlermethod)) {
log.error("【rpc系统异常-空指针】", e);
throw e;
}
log.error("【web系统异常-空指针】", e);
return result.error(errorcodeenum.system_error.getcode(), "数据加载异常,操作失败");
}
// 4、全局未知异常兜底
@exceptionhandler(exception.class)
public result<void> exceptionhandler(httpservletrequest request, handlermethod handlermethod, exception e) {
// rpc接口不兜底,原样抛出保证链路透传
if (isrpcapi(handlermethod)) {
log.error("【rpc未知异常】", e);
throw new businessexception(errorcodeenum.system_error);
}
// web接口统一友好兜底
log.error("【web未知系统异常】", e);
return result.error(errorcodeenum.system_error.getcode(), "系统繁忙,请稍后重试");
}
}4.4 接口使用示范
/**
* web接口:对外前端,友好兜底
*/
@restcontroller
@requestmapping("/web/user")
public class userwebcontroller {
@getmapping("/login")
public result<string> login(string username) {
if (username == null) {
throw new businessexception(errorcodeenum.param_error);
}
return result.success("登录成功");
}
}
/**
* rpc接口:对内服务调用,异常透传
*/
@restcontroller
@requestmapping("/rpc/user")
@rpcapi
public class userrpccontroller {
@getmapping("/getbyid")
public result<string> getuserbyid(long id) {
if (id == null) {
throw new businessexception(errorcodeenum.param_error);
}
return result.success("用户信息");
}
}五、web与rpc异常处理核心差异汇总表
处理维度 | web http 接口 | rpc 服务接口 | 差异化原因 |
|---|---|---|---|
异常返回方式 | 捕获异常,返回标准化json | 不捕获核心异常,主动上抛异常 | rpc框架依赖异常判定调用结果,吞异常会导致降级失效 |
错误码透传 | 简化展示,部分场景统一500 | 100%透传原始业务错误码 | 消费端需要根据错误码做不同业务降级、重试、提示 |
堆栈信息 | 生产环境屏蔽,不对外暴露 | 完整记录堆栈,用于微服务链路追踪 | 微服务调用链路长,无堆栈无法定位问题服务 |
日志策略 | 业务异常warn,系统异常error | 所有业务+系统异常均为error级别 | rpc调用失败均属于服务异常,需要重点监控告警 |
兜底策略 | 强兜底,杜绝原生报错 | 弱兜底,不掩盖真实异常 | rpc优先保证数据真实,web优先保证体验与安全 |
六、微服务高频踩坑与解决方案
线上踩坑问题 | 问题根源 | 解决方案 |
|---|---|---|
rpc调用永远返回成功,无法触发降级 | 全局异常统一捕获并返回json,吞掉了rpc异常 | rpc接口禁止捕获业务异常,主动上抛 |
微服务报错无堆栈,无法定位问题 | 照搬web异常逻辑,屏蔽了所有异常堆栈 | 差异化开启rpc堆栈日志记录 |
前端偶尔展示英文异常堆栈 | web接口异常未完全兜底,部分系统异常透传 | web接口全量捕获,统一友好提示 |
消费端无法获取服务端自定义错误码 | rpc异常被包装,原始错误码丢失 | rpc场景原样透传自定义businessexception |
异步@async rpc异常拦截失效 | 异步线程脱离mvc切面 | 异步rpc方法内部手动捕获并上抛框架异常 |
七、全文总结与企业落地规范
在微服务架构中,全局异常处理的核心不再是统一返回,而是场景化差异化适配。web接口面向用户,优先安全和体验;rpc接口面向服务,优先精准和溯源。
核心落地规范:
- 场景隔离:通过自定义注解区分web/rpc接口,两套异常逻辑解耦,互不干扰;
- web规范:全量兜底、屏蔽堆栈、友好提示、保障前端体验与项目安全;
- rpc规范:异常透传、保留堆栈、不吞报错、保障微服务降级重试机制生效;
- 日志规范:分级打印日志,rpc异常强制告警,web异常区分用户操作与系统bug;
- 架构规范:所有微服务必须采用双场景差异化异常方案,禁止web/rpc共用一套逻辑。
到此这篇关于springboot全局异常处理之web与rpc接口差异化兜底方案(微服务全覆盖)的文章就介绍到这了,更多相关springboot全局异常处理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论