javaSE,对于异常层面的认识
我们知道,代码是运行在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. 最佳实践建议
-
异常分类处理:
- HTTP 400:客户端参数错误
- HTTP 401/403:认证授权错误
- HTTP 404:资源不存在
- HTTP 500:服务器内部错误
-
异常信息国际化:
@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));
}
- 监控告警:
- 对系统异常进行监控
- 设置阈值告警
- 关键业务异常记录到专门日志
通过这样的设计,可以确保:
4. 接口异常不会中断服务流程
5. 前端获得友好的错误提示
6. 后端能够完整记录异常信息
7. 系统具备良好的可维护性和可观测性
特别在实际开发接口中使用这异常要注意的点:
明确异常类型:不直接用 catch (Exception e) 一把抓,而是根据可能抛出的具体异常类型来捕获,比如 IOException、SQLException。
日志一定要打:在 catch 里至少要把异常堆栈信息打印到日志里,用 log.error(“出错了”, e),不然线上出问题没法排查。
不要吞掉异常:最忌讳 catch 里什么都不写,或者只打印 e.getMessage()(这没堆栈信息),这叫“吞异常”,会坑死人。
能处理的处理,不能处理的抛:如果是业务上能处理的(比如用户输入格式不对),就给用户友好提示;如果是系统级错误(比如数据库连不上),要么抛出去让上层统一处理,要么降级返回默认值。
资源要释放:用 try-with-resources(JDK7+)自动关流,比手动在 finally 里关更安全简洁。”
更多推荐




所有评论(0)