我们知道,代码是运行在jvm里面的。但是要让接口能顺利运行下去,不会因为报错,而中断,或者报错了有给前端返回对应的问题。
我们来认识一下,java的异常体系,以及如何处理异常
1.java中有哪些类型的异常
2.如何处理这些异常
3.自定义处理异常,有哪些异常可以抓取
4.在实接口处理中,对异常处理有哪些原则。
5.使用springmvc的全局异常捕获,设计异常捕获,对接口返回,的设计思路和流程是什么
一、Java异常体系
1. 异常分类层次结构
Throwable (所有异常/错误的父类) ├── Error (系统级错误,一般程序无法处理) │ ├── VirtualMachineError │ ├── OutOfMemoryError │ └── StackOverflowError └── Exception (程序可处理的异常) ├── RuntimeException (运行时异常,非受检异常) │ ├── NullPointerException │ ├── IndexOutOfBoundsException │ ├── IllegalArgumentException │ └── ... └── CheckedException (受检异常,编译时检查) ├── IOException ├── SQLException ├── ClassNotFoundException └── ...2. 主要异常类型说明
RuntimeException(运行时异常/非受检异常):
- 代码逻辑问题导致,不强制捕获
- 常见子类:
NullPointerException:空指针IllegalArgumentException:非法参数IndexOutOfBoundsException:下标越界ClassCastException:类型转换错误
CheckedException(受检异常):
- 编译时强制检查,必须处理(try-catch或throws)
- 常见子类:
IOException:I/O操作异常SQLException:数据库操作异常ParseException:解析异常
Error(错误):
- JVM或系统严重问题,一般不需要捕获处理
二、异常处理方式
1. 基本处理机制
try{// 可能抛出异常的代码}catch(SpecificExceptione){// 处理特定异常}catch(Exceptione){// 处理其他异常}finally{// 无论是否异常都会执行(资源清理)}2. try-with-resources(Java 7+)
try(FileInputStreamfis=newFileInputStream("file.txt");BufferedReaderbr=newBufferedReader(newInputStreamReader(fis))){// 自动关闭资源}catch(IOExceptione){// 异常处理}3. throws声明
publicvoidreadFile()throwsIOException{// 方法内部不处理,由调用者处理}三、自定义异常处理
1. 自定义异常类
// 业务异常基类publicclassBusinessExceptionextendsRuntimeException{privateStringcode;privateStringmessage;publicBusinessException(Stringcode,Stringmessage){super(message);this.code=code;this.message=message;}}// 具体业务异常publicclassUserNotFoundExceptionextendsBusinessException{publicUserNotFoundException(){super("USER_NOT_FOUND","用户不存在");}}2. 可捕获的异常类型
@ControllerAdvicepublicclassGlobalExceptionHandler{// 1. 处理自定义业务异常@ExceptionHandler(BusinessException.class)// 2. 处理运行时异常@ExceptionHandler(RuntimeException.class)// 3. 处理受检异常@ExceptionHandler(IOException.class)@ExceptionHandler(SQLException.class)// 4. 处理参数验证异常@ExceptionHandler(MethodArgumentNotValidException.class)// 5. 处理未捕获的异常@ExceptionHandler(Exception.class)}四、接口异常处理原则
1.尽早抛出,晚点捕获
- 在能检测到问题时立即抛出异常
- 在合适的层次(通常是Controller层)统一处理
2.异常信息明确
// 不推荐thrownewException("操作失败");// 推荐thrownewBusinessException("ORDER_PAID_FAILED","订单支付失败,用户余额不足,订单号:"+orderNo);3.区分业务异常和系统异常
- 业务异常:用户操作不当、数据校验失败等,应该给用户友好提示
- 系统异常:数据库连接失败、网络超时等,记录日志,给用户通用错误提示
4.异常日志记录
@ExceptionHandler(Exception.class)publicResponseEntity<Result>handleException(Exceptione,HttpServletRequestrequest){log.error("请求URI: {}, 异常类型: {}, 异常信息: {}",request.getRequestURI(),e.getClass().getName(),e.getMessage(),e);// 记录完整堆栈// ...}5.不要吞掉异常
// 错误做法try{// ...}catch(Exceptione){// 什么都没做,异常被吞掉}// 正确做法try{// ...}catch(Exceptione){log.error("操作失败",e);thrownewBusinessException("OPERATION_FAILED","操作失败");}五、Spring MVC全局异常捕获设计
1. 统一返回结构
@Data@NoArgsConstructor@AllArgsConstructorpublicclassResult<T>{privatebooleansuccess;privateStringcode;privateStringmessage;privateTdata;privateLongtimestamp;publicstatic<T>Result<T>success(Tdata){returnnewResult<>(true,"SUCCESS","操作成功",data,System.currentTimeMillis());}publicstatic<T>Result<T>error(Stringcode,Stringmessage){returnnewResult<>(false,code,message,null,System.currentTimeMillis());}}2. 全局异常处理器
@Slf4j@RestControllerAdvicepublicclassGlobalExceptionHandler{/** * 处理业务异常 */@ExceptionHandler(BusinessException.class)publicResponseEntity<Result<Void>>handleBusinessException(BusinessExceptione,HttpServletRequestrequest){log.warn("业务异常 - URI: {}, Code: {}, Message: {}",request.getRequestURI(),e.getCode(),e.getMessage());returnResponseEntity.ok().body(Result.error(e.getCode(),e.getMessage()));}/** * 处理参数校验异常 */@ExceptionHandler(MethodArgumentNotValidException.class)publicResponseEntity<Result<Map<String,String>>>handleValidationException(MethodArgumentNotValidExceptione){Map<String,String>errors=newHashMap<>();e.getBindingResult().getFieldErrors().forEach(error->errors.put(error.getField(),error.getDefaultMessage()));returnResponseEntity.ok().body(Result.error("VALIDATION_FAILED","参数校验失败").data(errors));}/** * 处理系统异常 */@ExceptionHandler(Exception.class)publicResponseEntity<Result<Void>>handleSystemException(Exceptione,HttpServletRequestrequest){log.error("系统异常 - URI: {}, Exception: {}",request.getRequestURI(),e.getMessage(),e);// 生产环境返回通用错误信息,避免暴露系统细节Stringmessage="系统内部错误,请联系管理员";if("dev".equals(env)){message=e.getMessage();}returnResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Result.error("SYSTEM_ERROR",message));}}3. 业务层异常使用
@ServicepublicclassUserService{publicUsergetUserById(LonguserId){returnuserRepository.findById(userId).orElseThrow(()->newUserNotFoundException());}publicvoidcreateUser(UserCreateRequestrequest){// 参数校验if(StringUtils.isBlank(request.getUsername())){thrownewBusinessException("USERNAME_EMPTY","用户名不能为空");}// 业务逻辑校验if(userRepository.existsByUsername(request.getUsername())){thrownewBusinessException("USERNAME_EXISTS","用户名已存在");}// 业务操作try{Useruser=newUser();BeanUtils.copyProperties(request,user);userRepository.save(user);}catch(DataIntegrityViolationExceptione){thrownewBusinessException("DATA_INTEGRITY_ERROR","数据完整性错误");}}}4. 异常处理流程设计
客户端请求 ↓ Controller层接收请求 ↓ 参数验证(@Valid) → 失败 → 抛出MethodArgumentNotValidException ↓ Service层业务处理 → 业务异常 → 抛出BusinessException → 系统异常 → 抛出RuntimeException/SQLException等 ↓ Controller层返回正常结果 ↓ 异常发生 → 被@ExceptionHandler捕获 ↓ 全局异常处理器根据异常类型处理 ↓ 统一封装为Result对象 ↓ 返回给客户端5. 最佳实践建议
异常分类处理:
- HTTP 400:客户端参数错误
- HTTP 401/403:认证授权错误
- HTTP 404:资源不存在
- HTTP 500:服务器内部错误
异常信息国际化:
@ExceptionHandler(BusinessException.class)publicResponseEntity<Result<Void>>handleBusinessException(BusinessExceptione,Localelocale){Stringmessage=messageSource.getMessage("error."+e.getCode(),null,e.getMessage(),locale);returnResponseEntity.ok(Result.error(e.getCode(),message));}- 监控告警:
- 对系统异常进行监控
- 设置阈值告警
- 关键业务异常记录到专门日志
通过这样的设计,可以确保:
4. 接口异常不会中断服务流程
5. 前端获得友好的错误提示
6. 后端能够完整记录异常信息
7. 系统具备良好的可维护性和可观测性
特别在实际开发接口中使用这异常要注意的点:
明确异常类型:不直接用 catch (Exception e) 一把抓,而是根据可能抛出的具体异常类型来捕获,比如 IOException、SQLException。
日志一定要打:在 catch 里至少要把异常堆栈信息打印到日志里,用 log.error(“出错了”, e),不然线上出问题没法排查。
不要吞掉异常:最忌讳 catch 里什么都不写,或者只打印 e.getMessage()(这没堆栈信息),这叫“吞异常”,会坑死人。
能处理的处理,不能处理的抛:如果是业务上能处理的(比如用户输入格式不对),就给用户友好提示;如果是系统级错误(比如数据库连不上),要么抛出去让上层统一处理,要么降级返回默认值。
资源要释放:用 try-with-resources(JDK7+)自动关流,比手动在 finally 里关更安全简洁。”