一、引言:我大意了,没有闪!
你有没有过这种经历?
- 写了一堆
try-catch,结果上线后还是被用户投诉"点一下就白屏"? - 日志里突然冒出一堆
NullPointerException,你一脸懵:"这代码我写了三年,它怎么敢的啊?" - 第三方接口突然挂了,你的服务直接跟着"躺平",连个像样的提示都没有。
看下面几张图:


真实有吐血的感觉,程序若有生命,高低得来一句:来,骗!来,偷袭我这三五年的老程序!我大意了,没有闪!

其实,问题不在你写得不够快,而在异常处理没想清楚。今天我们就用一个用户注册的例子,把"救火式编码"变成"稳如老狗式架构"。
二、真实场景:用户注册,步步惊心
我们要实现一个简单的注册功能,流程如下:
- 校验邮箱格式
- 检查邮箱是否已存在
- 调用短信服务发验证码
- 保存用户到数据库
看起来平平无奇,但每一步都可能"翻车":
- 用户输了个
123@,邮箱格式不对 - 邮箱被人注册过了
- 短信服务商限流了,返回"请求太频繁"
- 数据库连接突然断了(别问,问就是运维在重启)
如果全靠 e.printStackTrace(),那系统就像纸糊的------风一吹就散。

三、我们的目标:稳住!
设计思路三句话:
- 业务错误 ≠ 系统崩溃:邮箱重复是正常业务场景,不是 bug!
- 前端要看得懂:不能返回"java.lang.NullPointerException",得说"亲,邮箱已被占用哦~"
- 统一出口,集中管理:别在每个方法里写 catch,搞个"异常接待处"!
分层异常流向示意图:

四、动手写代码:show you the code!
第一步:定义统一返回格式
java
public class ApiResponse<T> {
private int code;
private String message;
// 200 表示成功,其他为业务错误码
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "操作成功", data);
}
public static <T> ApiResponse<T> error(String msg) {
return new ApiResponse<>(500, msg, null);
}
// 构造函数、getter/setter 略
}
第二步:自定义业务异常(重点!)
java
public class BusinessException extends RuntimeException {
private final String errorCode; // 比如 "EMAIL_EXISTS"
public BusinessException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public String getErrorCode() { return errorCode; }
}
这样前端就能根据
errorCode做不同提示,比如弹窗 or 跳转。
第三步:Service 层只抛"有意义"的异常
java
@Service
public class UserService {
public void register(String email, String phone) {
if (!isValidEmail(email)) {
throw new BusinessException("INVALID_EMAIL", "邮箱格式不正确,请检查");
}
if (userRepository.existsByEmail(email)) {
throw new BusinessException("EMAIL_EXISTS", "该邮箱已经注册啦~");
}
try {
smsService.send(phone);
} catch (SmsTimeoutException e) {
// 第三方异常 → 转为业务异常,隐藏技术细节
throw new BusinessException("SMS_TIMEOUT", "短信发送超时,请稍后再试");
}
userRepository.save(new User(email, phone));
}
private boolean isValidEmail(String email) {
return email != null && email.matches("^[\\w.-]+@([\\w-]+\\.)+[\\w-]{2,}$");
}
}
第四步:全局异常处理器(终极保险)
java
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// 捕获业务异常 → 直接返回给前端
@ExceptionHandler(BusinessException.class)
public ApiResponse<?> handleBusiness(BusinessException e) {
return ApiResponse.error(e.getMessage());
}
// 捕获"意外惊喜"(比如 NPE、DB 断连)
@ExceptionHandler(Exception.class)
public ApiResponse<?> handleUnexpected(Exception e) {
log.error("【系统异常】请程序员速来救火!", e); // 记日志,带堆栈
return ApiResponse.error("系统开小差了,请稍后再试~");
}
}
现在,哪怕数据库崩了,用户看到的也是温暖提示,而不是一串红色报错。

五、进阶玩法:自动重试?安排!
有时候异常是"暂时的",比如网络抖动。我们可以用责任链模式自动处理:

代码示意:
java
public class RetryHandler implements ExceptionHandler {
public void handle(Exception e, Context ctx) {
if (e instanceof SmsServiceException && ctx.retryCount < 3) {
ctx.retry(); // 自动重试
} else if (next != null) {
next.handle(e, ctx); // 交给降级处理器
}
}
}
这样,用户根本感觉不到"失败",系统自己默默重试+兜底,稳得一批!

六、总结:异常处理三字经
- 不吞异常 :空
catch是埋雷,迟早炸 - 不露细节 :别让前端看到
at com.xxx... - 要分层:校验异常、业务异常、系统异常,各回各家
记住:好代码不是不抛异常,而是知道异常来了怎么办。
老兄我最后送大家一句话:年轻人不要太气盛!
搞错了,再来:
写代码如打拳,异常处理就是你的"闪避技能"。练好了,任他风吹雨打,我自岿然不动!
欢迎大家关注我,一起交流。