Hello,早上好,我是楼下小黑哥~

最近偶然间在看到 Spring 官方文档的时候,新学到一个注解 @ControllerAdvice,并且成功使用这个注解重构我们项目的对外 API 接口,去除繁琐的重复代码,使其开发更加优雅。

展示具体重构代码之前,我们先来看下原先对外 API 接口是如何开发的。

这个 API 接口主要是用来与我们 APP 交互,这个过程我们统一定义一个交互协议,APP 端与后台 API 接口统一都使用 JSON 格式。

另外后台 API 接口对 APP 返回时,统一一些错误码,APP 端需要根据相应错误码,在页面弹出一些提示。

下面展示一个查询用户信息返回的接口数据:

{"code": "000000","msg": "success","result": {"id": "1","name": "test"}
}

code代表对外的错误码,msg代表错误信息,result代表具体返回信息。

前端 APP 获取这个返回信息,首先判断接口返回 code是否为 000000,如果是代表查询成功,然后获取 result 信息作出相应的展示。否则,直接弹出相应的错误信息。

欢迎关注我的公众号:程序通事,获得日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客:studyidea.cn

重构之前

下面我们来看下,重构之前的,后台 API 层的如何编码。

/*** V1 版本** @return*/
@RequestMapping("testv1")
public APIResult testv1() {try {User user = new User();user.setId("1");user.setName("test");return APIResult.success(user);} catch (APPException e) {log.error("内部异常", e);return APIResult.error(e.getCode(), e.getMsg());} catch (Exception e) {log.error("系统异常", e);return APIResult.error(RetCodeEnum.FAILED);}
}

上面的代码其实很简单,内部统一封装了一个工具类 APIResult,然后用其包装具体的结果。

@Data
public class APIResult<T> implements Serializable {private static final long serialVersionUID = 4747774542107711845L;private String code;private String msg;private T result;public static <T> APIResult success(T result) {APIResult apiResult = new APIResult();apiResult.setResult(result);apiResult.setCode("000000");apiResult.setMsg("success");return apiResult;}public static APIResult error(String code, String msg) {APIResult apiResult = new APIResult();apiResult.setCode(code);apiResult.setMsg(msg);return apiResult;}public static APIResult error(RetCodeEnum codeEnum) {APIResult apiResult = new APIResult();apiResult.setCode(codeEnum.getCode());apiResult.setMsg(codeEnum.getMsg());return apiResult;}

除了这个以外,还定义一个异常对象 APPException,用来统一包装内部的各种异常。

上面的代码很简单,但是呢可以说比较繁琐,重复代码也比较多,每个接口都需要使用 try...catch 包装,然后使用 APIResult包括正常的返回信息与错误信息。

第二呢,接口对象只能返回 APIResult,真实业务对象只能隐藏在 APIResult中。这样不太优雅,另外不能很直观知道真实业务对象。

重构之后

下面我们开始重构上面的代码,主要目的是去除重复的那一坨try...catch 代码。

这次重构我们需要使用Spring 注解 @ControllerAdvice以及 ResponseBodyAdvice,我们先来看下重构的代码。

ps: ResponseBodyAdvice来自 Spring 4.2 API,如果各位同学需要使用这个的话,可能需要升级 Spring 版本。

改写返回信息

首先我们需要实现 ResponseBodyAdvice,实现我们自己的处理类。

@ControllerAdvice
public class CustomResponseAdvice implements ResponseBodyAdvice {/*** 是否需要处理返回结果* @param methodParameter* @param aClass* @return*/@Overridepublic boolean supports(MethodParameter methodParameter, Class aClass) {System.out.println("In supports() method of " + getClass().getSimpleName());return true;}/*** 处理返回结果* @param body* @param methodParameter* @param mediaType* @param aClass* @param serverHttpRequest* @param serverHttpResponse* @return*/@Overridepublic Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {System.out.println("In beforeBodyWrite() method of " + getClass().getSimpleName());if (body instanceof APIResult) {return body;}return APIResult.success(body);}
}

实现上面的接口,我们就可以在 beforeBodyWrite方法里,修改返回结果了。

上面代码中,只是简单使用 APIResult包装了返回结果,然后返回。其实我们还可以在此增加一些额外逻辑,比如说如接口返回信息由加密的需求,我们可以在这一层统一加密。

另外,这里判断一下 body 是否 APIResult类,如果是就直接返回,不做修改。

这么做一来兼容之前的老接口,这是因为默认情况下,我们自己实现的 CustomResponseAdvice类,将会对所有的 Controller 生效。

如果不做判断,以前的老接返回就会被包装了两层 APIResul,影响 APP 解析。

除此之外,如果大家担心这个修改对以前的老接口有影响的话,可以使用下面的方式,只对指定的方法生效。

首先自定义一个注解,比如说:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomResponse {
}

然后将其标注在需要改动的方法中,然后我们在 ResponseBodyAdvice#supports中判断具体方法上有没有自定义注解 CustomResponse,如果存在,返回 true,这就代表最后将会修改返回类。如果不存在,则返回 false,那么就会跟以前流程一样。

/*** 是否需要处理返回结果** @param methodParameter* @param aClass* @return*/
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {System.out.println("In supports() method of " + getClass().getSimpleName());Method method = methodParameter.getMethod();return method.isAnnotationPresent(CustomResponse.class);
}

全局异常处理

上面的代码重构之后,将重复代码抽取了出来,整体的代码就剩下我们的业务逻辑,这样就变得非常简洁优雅。

不过,上面的重构的代码,还是存在问题,主要是异常的处理。

如果上面的业务代码抛出了异常,那么接口将会返回堆栈错误信息,而不是我们定义的错误信息。所以下面我们这个,再次优化一下。

这次我们主要需要使用 @ExceptionHandler注解,这个注解需要与 @ControllerAdvice 一起使用。

@Slf4j
@ControllerAdvice
public class CustomExceptionHandler {@ExceptionHandler(Exception.class)@ResponseBodypublic APIResult handleException(Exception e) {log.error("系统异常", e);return APIResult.error(RetCodeEnum.FAILED);}@ExceptionHandler(APPException.class)@ResponseBodypublic APIResult handleAPPException(APPException e) {log.error("内部异常", e);return APIResult.error(e.getCode(), e.getMsg());}}

使用这个 @ExceptionHandler,将会拦截相应的异常,然后将会调用的相应方法处理异常。这里我们就使用 APIResult包装一些错误信息返回。

总结

我们可以使用 @ControllerAdviceResponseBodyAdvice 拦截返回结果,统一做出一些修改。这样就可以使用的业务代码非常简洁,优雅。

另外,针对业务代码的中,我们可以使用 @ExceptionHandler注解,统一做一个全局异常处理,这样就可以无缝的跟 ResponseBodyAdvice结合。

不过这里需要一点,我们实现的 ResponseBodyAdvice 类,一定需要跟 @ControllerAdvice配合一起使用哦,至于具体原因,下篇文章小黑哥分析原来的时候,再具体解释哦。敬请期待哦~

欢迎关注我的公众号:程序通事,获得日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客:studyidea.cn

年轻人不讲武德,竟然重构出这么优雅后台 API 接口相关推荐

  1. 年轻人不讲武德有多可怕?

    1 孩子,走你! ▼ 2 这都是什么阴间燃料 (素材来源网络,侵删) ▼ 3 年轻人不讲武德 (素材来源网络,侵删) ▼ 4 当你偷瞄喜欢的男生 ▼ 5 贝多芬:我入土这么都多年了 (素材来源网络,侵 ...

  2. cout不明确什么意思_年轻人不讲武德是什么梗和意思 年轻人不讲武德梗出处

    太极大师马保国的一句"年轻人不讲武德,偷袭我这个69岁的老人家"最近火了,很多人都在模仿这个句式,用法还挺广泛.那么年轻人不讲武德是什么梗?年轻人不讲武德梗的出处是什么?下面小编带 ...

  3. 年轻人不讲武德,TDengine边缘侧数据存储方案挑战SQLite

    上周,涛思数据与EMQ在线上Meetup上联合发布了工业互联网一体化解决方案,基于TDengine.EMQ X搭建一个集工业数据采集.汇聚.清洗.存储分析以及可视化展示等能力于一体的轻量级边缘计算工业 ...

  4. 年轻人不讲武德,一起聊聊List集合(一)

    文章目录 前言 一.List类图 二.源码剖析 1. ArrayList(此篇详解) 2. LinkedList 3. Vector 4. CopyOnWriteArrayList ~~   码上福利 ...

  5. 年轻人不讲武德,一起聊聊List集合(三)

    文章目录 前言 一.List类图 二.源码剖析 1. Vector(此篇详解) 2. ArrayList 3. LinkedList 4. CopyOnWriteArrayList ~~   码上福利 ...

  6. 再见,米哈游!原神社区防f12控制台调试代码全解(年轻人不讲武德)

    前言 刚刚逛原神社区,不经意间按到了 f12 打开了控制台,突然屏幕暗了,发生甚么事了! 我一看,嗷,原来是进到无限 debbuger 调试了,传统审查讲究点到为止,用了 debbuger 这还了得, ...

  7. 年轻人不讲武德,竟用Python让马老师表演闪电五连鞭!

    11月份的头条,是属于马保国的. 一位69岁的老同志,惨遭年轻人偷袭,不讲武德. 看看把老同志欺负的... 要不是马老师讲仁义讲道德,甩手就是一个五连鞭. 哈哈哈,所以本期我们就用Python给马保国 ...

  8. 年轻人不讲武德,一起聊聊List集合(五)

    文章目录 前言 一.List类图 二.集合总结 1. ArrayList与Vector集合区别 2. ArrayList与CopyOnWriteArrayList集合区别 3. 时间复杂度 / 空间复 ...

  9. 年轻人不讲武德!卢伟冰脱口秀:小米高端之路好自为之

    11月26日,Redmi正式发布千元精品Note系列新机,三剑齐发:Note 9 Pro.Note 9和Note 9 4G三款产品. Redmi Note 9 Pro国内首发旗舰规格的一亿像素相机HM ...

  10. 如何优雅设计 API 接口,实现统一格式返回?

    来源:老顾聊技术 前言 在移动互联网,分布式.微服务盛行的今天,现在项目绝大部分都采用的微服务框架,前后端分离方式, (题外话:前后端的工作职责越来越明确,现在的前端都称之为大前端,技术栈以及生态圈都 ...

最新文章

  1. Unity中创建本地多人游戏完整案例视频教程 Learn To Create A Local Multiplayer Game In Unity
  2. Python培训常识:Python面试中常被问到的几种设计模式要知道
  3. Postman(使用指南)
  4. mysql 5.5.39 安装_CentOS7.2安装mysql5.5.39
  5. 命令行参数怎么输入_太好用了!谷歌开源的命令行接口工具fire
  6. 单应矩阵,基本矩阵,本质矩阵
  7. 给定一个0-1串,请找到一个尽可能长的子串,其中包含的0与1的个数相等。
  8. mysql ken len_MySQL EXPLAIN
  9. 关于举办“全国大学生大数据技能竞赛”的通知
  10. 《DSP using MATLAB》Problem 7.26
  11. ueditor1.4.3 jsp版在ssh下的配置
  12. 3.4 tensorflow2实现两总体样本尺度参数的秩检验法——python实战
  13. macOS Monterey 12.0.1(21A559) 正式版三分区原版黑苹果镜像
  14. 计算机绘图相切,第九章计算机绘图基础.
  15. jsp实现文件下载,out = pageContext.pushBody();out.close();不用写到jsp中
  16. 华为防火墙忘记密码,使用console口更改密码
  17. Ubuntu 安装QT 教程
  18. 物联网数据多又杂?好用的数据可视化服务来了
  19. 切比雪夫不等式证明及应用
  20. 奇异值分解(SVD)方法求解最小二乘问题

热门文章

  1. 利用Nginx+Mono+Fastcgi代替IIS对Asp.Net进行反向代理
  2. [转]Windows Shell 编程 第五章 【来源:http://blog.csdn.net/wangqiulin123456/article/details/7987939】...
  3. 基于单片机的超市储物柜设计_毕业设计论-单片机储物柜
  4. 正则校验 4位数字_推荐收藏,工作中常用的正则表达式总结
  5. 拓端tecdat|R语言中基于混合数据抽样(MIDAS)回归的HAR-RV模型预测GDP增长
  6. 拓端tecdat|R语言使用最优聚类簇数k-medoids聚类进行客户细分
  7. L1-6 字母串 (15 分)
  8. 数据通信与计算机网络第2版,数据通信与计算机网络(第2版)习题1
  9. train_test_split按比例划分
  10. conda: command not found