本文目录导读:

在Java中处理接口异常,通常涉及接口定义、具体实现和调用方处理三个层面的配合,以下是几种核心的实践方案:
接口层:声明受检异常
在接口方法上使用 throws 声明可能抛出的异常,强制实现类处理或继续抛出。
public interface UserService {
User findUserById(Long id) throws UserNotFoundException, DataAccessException;
void createUser(User user) throws ValidationException, DuplicateUserException;
}
实现类:要么处理异常,要么继续抛出(与接口声明一致或更具体)。
public class UserServiceImpl implements UserService {
@Override
public User findUserById(Long id) throws UserNotFoundException, DataAccessException {
// 如果未找到,抛出UserNotFoundException
// 如果数据库连接失败,抛出DataAccessException
}
}
调用方:必须使用 try-catch 处理或继续抛出。
try {
User user = userService.findUserById(123L);
} catch (UserNotFoundException e) {
log.error("用户不存在: {}", e.getMessage());
// 返回友好的错误信息
} catch (DataAccessException e) {
log.error("系统异常: {}", e.getMessage());
throw new RuntimeException("系统繁忙,请稍后重试");
}
自定义业务异常
创建业务异常类,区分不同类型的错误场景。
// 基础业务异常
public class BusinessException extends RuntimeException {
private final Integer code;
private final String message;
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
// getters
}
// 具体异常子类
public class UserNotFoundException extends BusinessException {
public UserNotFoundException(Long userId) {
super(1001, "用户ID " + userId + " 不存在");
}
}
public class DuplicateUserException extends BusinessException {
public DuplicateUserException(String username) {
super(1002, "用户名 '" + username + "' 已被占用");
}
}
统一异常处理(最常用)
使用 Spring Boot 的 @ControllerAdvice 或 @RestControllerAdvice 全局处理异常,避免在每个接口中重复 try-catch。
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理自定义业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<Void>> handleBusinessException(BusinessException e) {
log.warn("业务异常: code={}, message={}", e.getCode(), e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.error(e.getCode(), e.getMessage()));
}
// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Void>> handleValidationException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.error(400, message));
}
// 处理运行时异常(兜底)
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleException(Exception e) {
log.error("系统异常: ", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error(500, "系统繁忙,请稍后重试"));
}
// 统一响应封装
@Data
@AllArgsConstructor
public static class ApiResponse<T> {
private Integer code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "success", data);
}
public static <T> ApiResponse<T> error(Integer code, String message) {
return new ApiResponse<>(code, message, null);
}
}
}
使用示例:接口实现中直接抛异常,无需 try-catch。
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<User>> getUser(@PathVariable Long id) {
User user = userService.findUserById(id); // 异常由全局处理器接管
return ResponseEntity.ok(ApiResponse.success(user));
}
}
处理远程/第三方接口异常
调用外部 API 时的常见处理模式:
public class ExternalApiClient {
private static final int MAX_RETRY = 3;
private static final long RETRY_DELAY_MS = 1000;
public String callExternalService(String param) {
Exception lastException = null;
for (int i = 0; i < MAX_RETRY; i++) {
try {
// 模拟HTTP调用
HttpPost request = new HttpPost("https://external-api.com/data");
HttpResponse response = httpClient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
return EntityUtils.toString(response.getEntity());
} else if (statusCode >= 500) {
// 服务器错误,尝试重试
Thread.sleep(RETRY_DELAY_MS * (i + 1)); // 指数退避
continue;
} else {
// 客户端错误(4xx),不重试
throw new ExternalApiException("接口请求失败,状态码: " + statusCode, statusCode);
}
} catch (IOException | InterruptedException e) {
lastException = e;
log.warn("第{}次调用失败: {}", i + 1, e.getMessage());
}
}
// 所有重试失败
throw new ExternalApiException("外部接口调用失败,已重试" + MAX_RETRY + "次",
503, lastException);
}
}
使用 Optional 代替异常(可选)
对于“可能不存在”的情况,使用 Optional 可以避免抛出异常。
public interface UserService {
Optional<User> findUserById(Long id);
}
// 调用方
User user = userService.findUserById(123L)
.orElseThrow(() -> new UserNotFoundException(123L));
| 场景 | 推荐方案 |
|---|---|
| 控制器层 | 使用 @RestControllerAdvice 统一处理,避免每个接口写 try-catch |
| 服务层 | 抛出自定义 RuntimeException(如 BusinessException) |
| 第三方接口 | 使用重试机制 + 熔断(如 Resilience4j) |
| 参数校验 | 使用 Bean Validation(@Valid)配合全局异常处理 |
| 非必要异常 | 使用 Optional 或返回空集合,避免用异常控制流程 |
核心原则:
- 可控异常:用自定义业务异常 + 全局处理器
- 不可控异常(网络、IO):记录日志、重试、降级、返回友好提示
- 不要吞异常:
catch(Exception e) {}是最坏的做法
这样既能保证接口的健壮性,又能给调用方返回一致的错误信息结构。