先搞懂:错误 vs 异常的核心区别
很多开发者刚开始写代码时,总把错误和异常当成一回事——结果处理起来一团乱。其实两者的本质完全不同:

错误(Error):是系统级的严重问题,比如OutOfMemoryError(内存溢出)、StackOverflowError(栈溢出),这类问题通常是程序无法恢复的,比如JVM崩溃,只能通过优化代码或扩容解决。
异常(Exception):是程序运行时的意外情况,比如参数错误、数据库连接失败、接口超时,这类问题是可以被捕获并处理的,比如提示用户“密码错误”或重试请求。
简单记:错误是“救不活”的,异常是“能抢救”的。处理策略的核心,就是把“能抢救”的异常精准捕获,把“救不活”的错误及时暴露。
接口层:参数校验与边界异常的拦截技巧
接口是用户请求的入口,80%的异常都来自参数问题——比如用户名太短、密码不符合规则、传入null值。这一步处理好了,能挡住大部分无效请求。
1. 用框架做“前置拦截”,避免重复校验
不要手动写if-else判断参数!用成熟的校验框架能省90%的代码:
– Java:用Hibernate Validator(配合Spring Boot的@Valid注解);
– Python:用Pydantic(FastAPI的默认校验工具)。
举个Java的例子:
// 请求参数类(用注解定义校验规则)
@Data
public class CreateUserRequest {
@NotNull(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名需2-20个字符")
private String username;
@NotNull(message = "密码不能为空")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$",
message = "密码需含大小写字母+数字,至少8位")
private String password;
}
// 接口层用@Valid拦截无效参数
@PostMapping("/user")
public ResponseEntity<User> createUser(@Valid @RequestBody CreateUserRequest request) {
// 不用再写if判断——无效参数会直接抛出MethodArgumentNotValidException
return userService.create(request);
}
2. 边界值异常:别等数据进了服务层才报错
比如查询用户时,用户ID是负数、分页参数pageSize超过100——这些边界问题要在接口层直接拦截。
用全局参数拦截器比在每个接口写判断更高效:
# FastAPI的依赖项实现全局边界校验
from fastapi import Depends, HTTPException
def validate_page_params(page: int = 1, page_size: int = 10):
if page < 1:
raise HTTPException(status_code=400, detail="page参数不能小于1")
if page_size > 100:
raise HTTPException(status_code=400, detail="pageSize不能超过100")
return {"page": page, "page_size": page_size}
# 接口中引用依赖项
@app.get("/users")
async def get_users(params: dict = Depends(validate_page_params)):
return userService.list(params["page"], params["page_size"])
服务层:异常的分层传递与精准捕获
服务层是业务逻辑的核心,异常处理的关键是“分层传递+精准捕获”——别把底层的技术异常(比如SQL异常)直接抛给前端!
1. 定义自定义异常类,告别“一刀切”
用自定义异常能携带更丰富的信息(比如错误码、业务描述),方便前端展示和问题定位。比如:
// Java自定义业务异常
public class BusinessException extends RuntimeException {
private int code; // 错误码(比如400=参数错,500=系统错)
private String message; // 业务错误信息
public BusinessException(int code, String message) {
super(message);
this.code = code;
this.message = message;
}
// getter方法
}
# Python自定义业务异常
class BusinessError(Exception):
def __init__(self, code: int, msg: str):
self.code = code
self.msg = msg
super().__init__(msg)
2. 全局异常处理器:把异常“统一收口”
在服务层抛出的异常,要在全局异常处理器里统一拦截——避免每个接口都写try-catch。
比如Spring Boot的全局处理器:
@RestControllerAdvice // 全局拦截Controller层异常
public class GlobalExceptionHandler {
// 拦截业务异常(自定义的BusinessException)
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessError(BusinessException e) {
ErrorResponse response = new ErrorResponse(e.getCode(), e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
// 拦截参数校验异常(Hibernate Validator抛出的)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleParamError(MethodArgumentNotValidException e) {
// 提取校验失败的第一条信息
String msg = e.getBindingResult().getFieldErrors().get(0).getDefaultMessage();
ErrorResponse response = new ErrorResponse(400, msg);
return ResponseEntity.badRequest().body(response);
}
// 拦截未知异常(兜底用)
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleUnknownError(Exception e) {
Log.error("未知异常:请求ID={}, 参数={}", getRequestId(), getRequestParams(), e);
ErrorResponse response = new ErrorResponse(500, "系统繁忙,请稍后重试");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
日志设计:让异常“开口说话”的3个关键
很多开发者的日志写得像“天书”——出了问题根本查不到原因。好的日志要满足:能快速定位到“谁在什么时候做了什么导致异常”。
1. 必须记录的5个字段
日志里少写“发生异常了”这种废话!一定要包含:
| 字段名 | 作用 | 示例 |
|————–|————————–|————————–|
| request_id | 链路追踪的唯一标识 | 123e4567-e89b-12d3-a456-426614174000 |
| user_id | 操作人ID(如有) | 1001 |
| timestamp | 异常发生时间 | 2025-08-24 19:50:04 |
| error_type | 异常类型 | BusinessException |
| request_params | 请求参数 | {“username”: “test”, “password”: “123”} |
2. 日志级别别乱标
- ERROR:系统级故障(比如数据库连接失败、接口超时);
- WARN:业务异常(比如用户不存在、余额不足);
- INFO:普通操作记录(比如用户登录成功)。
别把所有日志都标成ERROR——否则真的故障会被淹没!
3. 用“结构化日志”代替字符串拼接
比如用JSON格式记录日志,方便ELK(Elasticsearch+Logstash+Kibana)收集和分析:
// 用Logback的JSON编码器
<appender name="JSON_FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/app.json</file>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<fieldNames>
<timestamp>timestamp</timestamp>
<message>message</message>
<logger>logger</logger>
<level>level</level>
<stack_trace>stack_trace</stack_trace>
</fieldNames>
</encoder>
</appender>
告警与恢复:从发现异常到解决的闭环设计
异常处理的终极目标是“不让异常变成故障”——要做到“早发现、早解决”。
1. 定义告警规则,别等用户投诉
根据异常类型设置阈值:
– ERROR级别日志:1分钟内超过5次,触发钉钉/飞书告警;
– 接口超时:超时(>2秒)请求占比超过10%,触发电话告警;
– 数据库连接池:占用率超过80%,触发邮件告警。
2. 用指标监控异常,而非仅日志
比如用Prometheus采集异常次数的指标,用Grafana做可视化 dashboard:
// Java用Micrometer记录异常指标
@Autowired
private MeterRegistry meterRegistry;
public void doBusiness() {
try {
// 业务逻辑
} catch (BusinessException e) {
// 记录异常次数(按错误码分类)
Counter.builder("business_exception_total")
.tag("error_code", String.valueOf(e.getCode()))
.tag("scene", "create_user")
.register(meterRegistry)
.increment();
throw e;
}
}
3. 故障恢复:自动重试 vs 快速失败
不是所有异常都要重试!比如:
– 可重试的异常:网络波动导致的接口超时、数据库死锁;
– 不可重试的异常:参数错误、业务逻辑错误(比如余额不足)。
举个Python的重试例子(用tenacity库):
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
@retry(stop=stop_after_attempt(3), # 最多重试3次
wait=wait_exponential(multiplier=1, min=1, max=10), # 重试间隔指数增长(1s→2s→4s)
retry=retry_if_exception_type((ConnectionError, TimeoutError))) # 只重试网络异常
def call_third_party_api():
# 调用第三方接口的逻辑
response = requests.get("https://api.example.com/data")
response.raise_for_status()
return response.json()
Java/Python:常见场景的代码实现
最后给两个高频场景的完整代码,直接抄走用:
场景1:Java接口参数校验+全局异常处理
// 1. 请求参数类
@Data
public class UpdateUserRequest {
@NotNull(message = "用户ID不能为空")
private Long userId;
@Email(message = "邮箱格式不正确")
private String email;
}
// 2. 接口层
@PutMapping("/user")
public ResponseEntity<String> updateUser(@Valid @RequestBody UpdateUserRequest request) {
userService.update(request);
return ResponseEntity.ok("修改成功");
}
// 3. 全局异常处理器(拦截参数错误)
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleParamError(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors().get(0).getDefaultMessage();
return ResponseEntity.badRequest().body(new ErrorResponse(400, msg));
}
}
场景2:Python FastAPI自定义异常
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
app = FastAPI()
# 1. 自定义业务异常
class BusinessError(HTTPException):
def __init__(self, code: int, msg: str):
super().__init__(status_code=code, detail=msg)
# 2. 请求参数类
class CreateOrderRequest(BaseModel):
product_id: int = Field(..., gt=0, description="商品ID必须大于0")
quantity: int = Field(..., gt=0, le=10, description="购买数量1-10件")
# 3. 接口层(抛出自定义异常)
@app.post("/order")
async def create_order(request: CreateOrderRequest):
# 模拟库存不足的情况
if request.product_id == 1001 and request.quantity > 5:
raise BusinessError(code=400, msg="该商品库存不足")
return {"order_id": 12345}
# 4. 全局异常处理器
@app.exception_handler(BusinessError)
async def handle_business_error(request, exc):
return JSONResponse(
status_code=exc.status_code,
content={"code": exc.status_code, "message": exc.detail}
)
原创文章,作者:,如若转载,请注明出处:https://zube.cn/archives/323