我们知道,代码是运行在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 (SpecificException e) {
    // 处理特定异常
} catch (Exception e) {
    // 处理其他异常
} finally {
    // 无论是否异常都会执行(资源清理)
}

2. try-with-resources(Java 7+)

try (FileInputStream fis = new FileInputStream("file.txt");
     BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    // 自动关闭资源
} catch (IOException e) {
    // 异常处理
}

3. throws声明

public void readFile() throws IOException {
    // 方法内部不处理,由调用者处理
}

三、自定义异常处理

1. 自定义异常类

// 业务异常基类
public class BusinessException extends RuntimeException {
    private String code;
    private String message;
    
    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
        this.message = message;
    }
}

// 具体业务异常
public class UserNotFoundException extends BusinessException {
    public UserNotFoundException() {
        super("USER_NOT_FOUND", "用户不存在");
    }
}

2. 可捕获的异常类型

@ControllerAdvice
public class GlobalExceptionHandler {
    
    // 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. 异常信息明确

// 不推荐
throw new Exception("操作失败");

// 推荐
throw new BusinessException("ORDER_PAID_FAILED", 
    "订单支付失败,用户余额不足,订单号:" + orderNo);

3. 区分业务异常和系统异常

  • 业务异常:用户操作不当、数据校验失败等,应该给用户友好提示
  • 系统异常:数据库连接失败、网络超时等,记录日志,给用户通用错误提示

4. 异常日志记录

@ExceptionHandler(Exception.class)
public ResponseEntity<Result> handleException(Exception e, HttpServletRequest request) {
    log.error("请求URI: {}, 异常类型: {}, 异常信息: {}", 
        request.getRequestURI(), 
        e.getClass().getName(),
        e.getMessage(),
        e); // 记录完整堆栈
    // ...
}

5. 不要吞掉异常

// 错误做法
try {
    // ...
} catch (Exception e) {
    // 什么都没做,异常被吞掉
}

// 正确做法
try {
    // ...
} catch (Exception e) {
    log.error("操作失败", e);
    throw new BusinessException("OPERATION_FAILED", "操作失败");
}

五、Spring MVC全局异常捕获设计

1. 统一返回结构

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    private boolean success;
    private String code;
    private String message;
    private T data;
    private Long timestamp;
    
    public static <T> Result<T> success(T data) {
        return new Result<>(true, "SUCCESS", "操作成功", data, System.currentTimeMillis());
    }
    
    public static <T> Result<T> error(String code, String message) {
        return new Result<>(false, code, message, null, System.currentTimeMillis());
    }
}

2. 全局异常处理器

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Result<Void>> handleBusinessException(
            BusinessException e, HttpServletRequest request) {
        
        log.warn("业务异常 - URI: {}, Code: {}, Message: {}", 
            request.getRequestURI(), e.getCode(), e.getMessage());
        
        return ResponseEntity.ok()
                .body(Result.error(e.getCode(), e.getMessage()));
    }
    
    /**
     * 处理参数校验异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Result<Map<String, String>>> handleValidationException(
            MethodArgumentNotValidException e) {
        
        Map<String, String> errors = new HashMap<>();
        e.getBindingResult().getFieldErrors().forEach(error ->
            errors.put(error.getField(), error.getDefaultMessage()));
        
        return ResponseEntity.ok()
                .body(Result.error("VALIDATION_FAILED", "参数校验失败")
                    .data(errors));
    }
    
    /**
     * 处理系统异常
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Result<Void>> handleSystemException(
            Exception e, HttpServletRequest request) {
        
        log.error("系统异常 - URI: {}, Exception: {}", 
            request.getRequestURI(), e.getMessage(), e);
        
        // 生产环境返回通用错误信息,避免暴露系统细节
        String message = "系统内部错误,请联系管理员";
        if ("dev".equals(env)) {
            message = e.getMessage();
        }
        
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(Result.error("SYSTEM_ERROR", message));
    }
}

3. 业务层异常使用

@Service
public class UserService {
    
    public User getUserById(Long userId) {
        return userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException());
    }
    
    public void createUser(UserCreateRequest request) {
        // 参数校验
        if (StringUtils.isBlank(request.getUsername())) {
            throw new BusinessException("USERNAME_EMPTY", "用户名不能为空");
        }
        
        // 业务逻辑校验
        if (userRepository.existsByUsername(request.getUsername())) {
            throw new BusinessException("USERNAME_EXISTS", "用户名已存在");
        }
        
        // 业务操作
        try {
            User user = new User();
            BeanUtils.copyProperties(request, user);
            userRepository.save(user);
        } catch (DataIntegrityViolationException e) {
            throw new BusinessException("DATA_INTEGRITY_ERROR", "数据完整性错误");
        }
    }
}

4. 异常处理流程设计

客户端请求
    ↓
Controller层接收请求
    ↓
参数验证(@Valid) → 失败 → 抛出MethodArgumentNotValidException
    ↓
Service层业务处理 → 业务异常 → 抛出BusinessException
                  → 系统异常 → 抛出RuntimeException/SQLException等
    ↓
Controller层返回正常结果
    ↓
异常发生 → 被@ExceptionHandler捕获
    ↓
全局异常处理器根据异常类型处理
    ↓
统一封装为Result对象
    ↓
返回给客户端

5. 最佳实践建议

  1. 异常分类处理

    • HTTP 400:客户端参数错误
    • HTTP 401/403:认证授权错误
    • HTTP 404:资源不存在
    • HTTP 500:服务器内部错误
  2. 异常信息国际化

@ExceptionHandler(BusinessException.class)
public ResponseEntity<Result<Void>> handleBusinessException(
        BusinessException e, Locale locale) {
    String message = messageSource.getMessage(
        "error." + e.getCode(), 
        null, 
        e.getMessage(), 
        locale
    );
    return ResponseEntity.ok(Result.error(e.getCode(), message));
}
  1. 监控告警
    • 对系统异常进行监控
    • 设置阈值告警
    • 关键业务异常记录到专门日志

通过这样的设计,可以确保:
4. 接口异常不会中断服务流程
5. 前端获得友好的错误提示
6. 后端能够完整记录异常信息
7. 系统具备良好的可维护性和可观测性

特别在实际开发接口中使用这异常要注意的点:
明确异常类型:不直接用 catch (Exception e) 一把抓,而是根据可能抛出的具体异常类型来捕获,比如 IOException、SQLException。

日志一定要打:在 catch 里至少要把异常堆栈信息打印到日志里,用 log.error(“出错了”, e),不然线上出问题没法排查。

不要吞掉异常:最忌讳 catch 里什么都不写,或者只打印 e.getMessage()(这没堆栈信息),这叫“吞异常”,会坑死人。

能处理的处理,不能处理的抛:如果是业务上能处理的(比如用户输入格式不对),就给用户友好提示;如果是系统级错误(比如数据库连不上),要么抛出去让上层统一处理,要么降级返回默认值。

资源要释放:用 try-with-resources(JDK7+)自动关流,比手动在 finally 里关更安全简洁。”

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐