本文中,我会描述如何在应用程序的不同层次,优雅地处理业务异常。
BusinessException基类定义如下,注意异常中携带业务错误码,方便前端处理异常:
public class BusinessException extends RuntimeException {
private final int errorCode;
public BusinessException(int errorCode) {
this.errorCode = errorCode;
}
public BusinessException(int errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public BusinessException(int errorCode, String message, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
public BusinessException(int errorCode, Throwable cause) {
super(cause);
this.errorCode = errorCode;
}
public BusinessException(int errorCode, String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
this.errorCode = errorCode;
}
public int getErrorCode() {
return errorCode;
}
}
具体的业务异常继承BusinessException,提供具体的错误码,以及注意提供具体的异常信息:
public class BookNotFoundException extends BusinessException {
public BookNotFoundException(Long id) {
super(ErrorCodes.BOOK_NOT_FOUND, "book [%s] not found".formatted(id));
}
}
我们需要返回给前端一个DTO,携带具体的错误信息,定义如下:
@Data
public class ApiError {
private int errorCode;
private String message;
private Object detail;
public ApiError() {}
public ApiError(int errorCode, String message, Object detail) {
this.errorCode = errorCode;
this.message = message;
this.detail = detail;
}
public ApiError(int errorCode, String message) {
this(errorCode, message, null);
}
//从业务异常转为DTO
public static ApiError valueOf(BusinessException e) {
return new ApiError(e.getErrorCode(), e.getMessage());
}
}
Service层仅负责抛出业务异常。
由于Service层应该专注于业务逻辑,不应该出现Http状态码相关的处理逻辑。
例如:
@Service
public class BookService {
private final Map<Long, Book> bookMap = new ConcurrentHashMap<>();
public Book getBookById(Long id) throws BookNotFoundException {
var book = bookMap.get(id);
if (book == null) {
//抛出业务异常,即找不到对应id的Book
throw new BookNotFoundException(id);
}
return book;
}
}
在Controller层,我们需要处理业务异常,返回相应的Http状态码以及异常信息DTO。
例如:
@RequestMapping("/book")
@RestController
public class BookController {
private final BookService service;
public BookController(BookService service) {
this.service = service;
}
//正常情况下200,返回数据
@GetMapping("/{id}")
public Book getBookById(@PathVariable Long id) {
return service.getBookById(id);
}
//返回404 NOT_FOUND状态码
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(BookNotFoundException.class)
public ApiError bookNotFound(BookNotFoundException e) {
//将异常转为ApiError对象,返回给客户端
return ApiError.valueOf(e);
}
}
访问 GET /book/{id} 获取对应id的书本
正常情况:
具体可以参考github:
https://github.com/superOTAKU/spring-rest-template