Spring boot统一错误处理
Spring boot统一错误处理
期望定义一种规范,能让前端非常方便地处理接口错误。那么问题来了,前端怎么处理接口比较方便呢?
前端脚手架通常会为所有的API请求写一个拦截器,其中拦截器很重要的一个能力就是对数据进行预处理。假设所有的正确数据都走向ajax
的success
句柄,所有异常的数据都走向error
句柄,成功或者失败的结果不会互相穿插,我们在定义句柄时无疑可以省却很多if-else
分支。
这是其一,其二:所有成功的数据都是ResultEnry
格式,所有的错误都是errorcode+errormsg
散列。我们在前端拦截器error
句柄可以凭借errorcode
联动错误信息配置非常方便的进行报错弹窗,省去大量接口类异常的逻辑代码。
所以此处统一错误处理应满足两点:
- 后台接口不应该捕获并处理数据异常
- 抛给前端的异常统一格式
自定义返回实体
略。参考另一篇《Spring boot自定义返回实体》。
统一异常处理的两种方法
- 使用注解
ControllerAdvice
- 实现
ErrorController
本例选择ControllerAdvice+自定义异常实现统一规范。下面简单介绍两种方式。
ControllerAdvice
该注解定义一个异常类,类里面定义多种异常句柄。
@ControllerAdvice
public class WebExceptionHandler {
@ExceptionHandler
public Result<Object> unknownException(Exception e) {
return ResultEntry.error(ResultEnum.UNKNOWN_ERROR);
}
@ExceptionHandler
public Result<Object> ioException(IOException e) {
return ResultEntry.error(ResultEnum.UNKNOWN_ERROR);
}
}
此方法只能监听到控制器抛出的异常
ErrorController
此方式会让框架默认的BaseErrorController
无效
@RestController
public class WebExceptionHandler implements ErrorController {
public Result<Object> error(HttpServletRequest request, HttpServletResponse response) {
return ResultEntry.error(ResultEnum.UNKNOWN_ERROR);
}
}
自定义运行时异常
业务代码捕捉到异常,然后通过自定义异常层层往上抛,结合状态码枚举ResultEnum
可以达到统一格式精准提示。
自定义异常ServiceException
:
public class ServiceException extends RuntimeException{
private ResultEnum error;
public ServiceException(ResultEnum error) {
this.error = error;
}
public ResultEnum getError() {
return error;
}
public void setError(ResultEnum error) {
this.error = error;
}
}
ControllerAdvice
异常句柄
@ControllerAdvice
public class ServiceExceptionHander {
@ExceptionHandler(ServiceException.class)
@ResponseBody
public <T> ResponseEntity<Result<T>> handle(ServiceException e){
ResponseEntity<Result<T>> result = ResultEntry.error(e.getError());
return result;
}
}
ResultEntry
组装返回的数据结构,利用ResponseEntity
传递给前端错误码和错误实体。所以如果接口能拿到正确的数据,返回HttpStatus.OK
,否则返回HttpStatus.BAD_GATEWAY
;前端也会相应的触发ajax
的success
或者error
句柄。
public class ResultEntry<T> implements Serializable{
public static <T> ResponseEntity<Result<T>> success(T o) {
return ResultEntry.response(ResultEnum.SUCCESS, o);
}
public static <T> ResponseEntity<Result<T>> success() {
return ResultEntry.response(ResultEnum.SUCCESS);
}
public static <T> ResponseEntity<Result<T>> response(ResultEnum enu, T o) {
Result<T> result = new Result<T>();
result.setMsg(enu.getMsg());
result.setStatus(enu.getStatus());
result.setBody(o);
return new ResponseEntity<Result<T>>(result, HttpStatus.OK);
}
public static <T> ResponseEntity<Result<T>> response(ResultEnum enu) {
Result<T> result = new Result<T>();
result.setMsg(enu.getMsg());
result.setStatus(enu.getStatus());
return new ResponseEntity<Result<T>>(HttpStatus.OK);
}
public static <T> ResponseEntity<Result<T>> error(ResultEnum enu) {
Result<T> result = new Result<T>();
result.setMsg(enu.getMsg());
result.setStatus(enu.getStatus());
return new ResponseEntity<Result<T>>(result, HttpStatus.BAD_GATEWAY);
}
}
测试
//controller
@RestController()
public class UpgradeController {
@Autowired
private UpgradeService upgradeService;
@RequestMapping(value = "upgrade/records", method = RequestMethod.GET)
public ResponseEntity<Result<ResultPage<UpgradeRecordDTO>>> getUpgradeRecords() {
ResultPage<UpgradeRecordDTO> data = upgradeService.getUpgradeRecords();
return ResultEntry.success(data);
}
}
//service
@Service
public class UpgradeService {
@Autowired
private UpgradeRecordRepository recordRepository;
// 查升级记录
public ResultPage<UpgradeRecordDTO> getUpgradeRecords() throws ServiceException {
Pageable pageable = PageRequest.of(0, 10);
return new ResultPage<>(recordRepository.findAll(pageable));
}
}
敌人总是会在你最不想它出现的地方出现!