写点什么

字节跳动面试官:SpringBoot 统一接口返回和全局异常处理怎么玩?

  • 2021 年 11 月 21 日
  • 本文字数:3890 字

    阅读完需:约 13 分钟

现在大多数公司项目框架,基本都是属于前后端分离模式,这种模式会涉及到一个前后端对接问题,无论是对前端或者是后台服务,维护一套完善且规范的接口是非常有必要的,这样不仅能够提高对接效率,也可以让我的代码看起来更加简洁优雅。


修改前后最大的区别是我们不用在每个接口单独捕获异常,也不用在每个接口都要组装一遍返回参数,可以参考下面这张对比图:


字节跳动面试官:SpringBoot 统一接口返回和全局异常处理怎么玩?一、SpringBoot 不使用统一返回格式默认情况下,SpringBoot 会有如下三种返回情况。


1.1 字符串 @GetMapping("/getUserName")public String getUserName(){return "HuaGe";}调用接口返回结果:


HuaGe1.2 实体类 @GetMapping("/getUserName")public User getUserName(){return new User("HuaGe",18,"男");}调用接口返回结果:


{"name": "HuaGe","age": "18","性别": "男",}1.3 异常返回 @GetMapping("/getUserName")public static String getUserName(){HashMap hashMap = Maps.newHashMap();return hashMap.get(0).toString();}模拟一个空指针异常,在不做任何异常处理的情况下,可以看下 SpringBoot 的默认返回结果:


{"timestamp": "2021-08-09T06:56:41.524+00:00","status": 500,"error": "Internal Server Error","path": "/sysUser/getUserName"}对于上面这几种情况,如果整个项目没有定义统一的返回格式,五个后台开发人员定义五种返回格式,这样不仅代码臃肿,前后端对接效率低,而且还会有一些意向不到的情况发生,比如前端直接显示异常详情等,这给用户的体验是非常差的。


二、基础玩法项目中最常见到的是封装一个工具类,类中定义需要返回的字段信息,把需要返回前端的接口信息,通过该类进行封装,这样就可以解决返回格式不统一的现象了。


2.1 参数说明 code: 状态码,后台可以维护一套统一的状态码;message: 描述信息,接口调用成功/失败的提示信息;data: 返回数据。2.2 流程说明新建 Result 类 public class Result<T> {


private int code;
private String message;
private T data;
public Result() {}public Result(int code, String message) { this.code = code; this.message = message;}
/** * 成功 */public static <T> Result<T> success(T data) { Result<T> result = new Result<T>(); result.setCode(ResultMsgEnum.SUCCESS.getCode()); result.setMessage(ResultMsgEnum.SUCCESS.getMessage()); result.setData(data); return result;}
/** * 失败 */public static <T> Result<T> error(int code, String message) { return new Result(code, message);}
复制代码


}定义返回状态码 public enum ResultMsgEnum {SUCCESS(0, "成功"),FAIL(-1, "失败"),AUTH_ERROR(502, "授权失败!"),SERVER_BUSY(503, "服务器正忙,请稍后再试!"),DATABASE_OPERATION_FAILED(504, "数据库操作失败");private int code;private String message;​ResultMsgEnum(int code, String message) {this.code = code;this.message = message;}public int getCode() {return this.code;}


public String getMessage() {    return this.message;}
复制代码


}使用方式上面两步定义了数据返回格式和状态码,接下来就要看下在接口中如何使用了。


@GetMapping("/getUserName")public Result getUserName(){return Result.success("huage");}调用结果如下,可以看到是我们在 Result 中定义的参数类型。


{"code": 0,"message": "成功","data": "huage"}这样写虽然能够满足日常需求,而且我相信很多小伙伴也是这么用的,但是如果我们有大量的接口,然后在每一个接口中都使用 Result.success 来包装返回信息,会新增很多重复代码,显得不够优雅,甚至都不好意思拿出去显摆。 肯定会有一种方式能够再一次提高代码逼格,实现最优解。


字节跳动面试官:SpringBoot 统一接口返回和全局异常处理怎么玩?三、进阶用法基本用法学会后,接下来看点究极版本,主要用到如下两个知识点,用法简单,无论是拿出来教学妹,还是指点小姐姐,都是必备技能。


3.1 类介绍 3.1 类介绍 3.1 类介绍 ResponseBodyAdvice: 该接口是 SpringMVC 4.1 提供的,它允许在 执行 @ResponseBody 后自定义返回数据,用来封装统一数据格式返回;@RestControllerAdvice: 该注解是对 Controller 进行增强的,可以全局捕获抛出的异常。3.2 用法说明新建 ResponseAdvice 类;实现 ResponseBodyAdvice 接口,实现 supports、beforeBodyWrite 方法;该类用于统一封装 controller 中接口的返回结果。@RestControllerAdvicepublic class ResponseAdvice implements ResponseBodyAdvice<Object> {@Autowiredprivate ObjectMapper objectMapper;​/*** 是否开启功能 true:是/@Overridepublic boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {return true;}​/** 处理返回结果*/@Overridepublic Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {//处理字符串类型数据 if(o instanceof String){try {return objectMapper.writeValueAsString(Result.success(o));} catch (JsonProcessingException e) {e.printStackTrace();}}return Result.success(o);}}​我们可以通过 getUserName 接口测试一下,会发现和直接使用 Result 返回的结果是一致的。


不过,细心的小伙伴们肯定注意到了,在 ResponseAdvice 我们全部使用了 Result.success(o)来处理结果,对于 error 类型的结果未做处理。我们来看下,发生异常情况时,返回结果是什么样呢?继续使用上面 HashMap 空指针异常的代码,测试结果如下:


{"code": 0,"message": "成功","data": {"timestamp": "2021-08-09T09:33:26.805+00:00","status": 405,"error": "Method Not Allowed","path": "/sysUser/getUserName"}}虽然格式上没有毛病,但是在 code、data 字段的具体数据上是不友好或不正确的。不处理好这些事情,会严重影响自己在前端妹妹心中的高大形象的,这是决不能容忍的。


3.3 全局异常处理器以前我们遇到异常时,第一时间想到的应该是 try..catch..finnal 吧,不过这种方式会导致大量代码重复,维护困难,逻辑臃肿等问题,这不是我们想要的结果。


今天我们要用的全局异常处理方式,用起来是比较简单的。首先新增一个类,增加 @RestControllerAdvice 注解,该注解的作用花哥上面已经介绍过,就不再唠叨了。


@RestControllerAdvicepublic class CustomerExceptionHandler {


}如果我们有想要拦截的异常类型,就新增一个方法,使用 @ExceptionHandler 注解修饰,注解参数为目标异常类型。


例如:controller 中接口发生 Exception 异常时,就会进入到 Execption 方法中进行捕获,将杂乱的异常信息,转换成指定格式后交给 ResponseAdvice 方法进行统一格式封装并返回给前端小伙伴。


@RestControllerAdvice@Slf4jpublic class CustomerExceptionHandler {​@ExceptionHandler(AuthException.class)public String ErrorHandler(AuthorizationException e) {log.error("没有通过权限验证!", e);return "没有通过权限验证!";}​@ExceptionHandler(Exception.class)public Result Execption(Exception e) {log.error("未知异常!", e);return Result.error(ResultMsgEnum.SERVER_BUSY.getCode(),ResultMsgEnum.SERVER_BUSY.getMessage());}}再次调用接口 getUserName 查看返回结果,会发现还是有一些问题,因为我们在 CustomerExceptionHandler 中已经将接口返回结果封装成 Result 类型,而代码执行到统一结果返回类 ResponseAdvice 时,又会结果再次封装,就出现了如下问题。


{"code": 0,"message": "成功","data": {"code": 503,"message": "服务器正忙,请稍后再试!","data": null}}3.4 统一返回结果处理类最终版解决上述问题非常简单,只要在 beforeBodyWrite 中增加一条判断即可。


@RestControllerAdvicepublic class ResponseAdvice implements ResponseBodyAdvice<Object> {@Autowiredprivate ObjectMapper objectMapper;​/*** 是否开启功能 true:开启/@Overridepublic boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {return true;}​/** 处理返回结果*/@Overridepublic Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {//处理字符串类型数据 if(o instanceof String){try {return objectMapper.writeValueAsString(Result.success(o));} catch (JsonProcessingException e) {e.printStackTrace();}}//返回类型是否已经封装 if(o instanceof Result){return o;}return Result.success(o);}}​至此,本章的任务就全部讲完,上述代码可以直接引用,不需要其他的配置项,非常推荐引用到自己的项目中。


四、总结本章讲解的内容不是很多,主要就两个类的配置,即处理统一返回结果的类 ResponseAdvice 和异常处理类 CustomerExceptionHandler。全文围绕 RestControllerAdvice、ResponseBodyAdvice 的使用,通过一步步的迭代,最终构建一套通用的代码代码返回格式。本文属于实用知识分享,不知道小伙伴们还有其他好用的统一处理方案嘛,如果有更好的方案,可以分享出来大家一块学习。


字节跳动面试官:SpringBoot 统一接口返回和全局异常处理怎么玩?

用户头像

还未添加个人签名 2021.10.14 加入

还未添加个人简介

评论

发布
暂无评论
字节跳动面试官:SpringBoot统一接口返回和全局异常处理怎么玩?