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
*/
@Override
public 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
*/
@Override
public 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)
@ResponseBody
public APIResult handleException(Exception e) {
log.error("系统异常", e);
return APIResult.error(RetCodeEnum.FAILED);
} @ExceptionHandler(APPException.class)
@ResponseBody
public 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. 年轻人不讲武德来白piao我这个老同志

    朋友们好啊,我是码农小胖哥. 今天有个同学问我在吗,我说什么事? 给我发个截图,我一看!噢,原来是帮忙搞个定时任务,还是动态的. 他说了两种选择,一种是用DelayQueue,一种是用消息队列. 他说 ...

  2. Python爬取B站耗子尾汁、不讲武德出处的视频弹幕

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. 前言 耗子喂汁是什么意思什么梗呢?可能很多人不知道,这个梗是出自马保国,经常上网的人可能听说过这个 ...

  3. 如何设计出优秀的Restful API?

    https://mp.weixin.qq.com/s?__biz=MzU0OTE4MzYzMw==&mid=2247485240&idx=1&sn=b5b9c8c41659d2 ...

  4. JLC PCB 嘉立创自动确认生产稿,不讲武德?耗子尾汁!!!

    首先,开局一张图,嘉立创又不做人的一天.嘉立创不讲武德,耗子尾汁!!! 之前下单,勾选了确定生产稿和不加客编,结果生产稿出来还是给我加了客编.那我出10元的意思何在?让我自己花3元看我花的10元有没有 ...

  5. 流程自动化RPA,Power Automate Desktop系列 - 不讲武德的Windows10内置应用

    简介 Power Automate Desktop 扩展Power Automate中的现有机器人流程自动化(RPA)功能,并使您能够自动化所有重复的桌面流程. 使用预生成的拖放操作或记录您自己的桌面 ...

  6. go 学习笔记之万万没想到宠物店竟然催生出面向接口编程?

    到底是要猫还是要狗 在上篇文章中,我们编撰了一则简短的小故事用于讲解了什么是面向对象的继承特性以及 Go 语言是如何实现这种继承语义的,这一节我们将继续探讨新的场景,希望能顺便讲解面向对象的接口概念. ...

  7. 使用Guava retryer优雅的实现接口重试机制

    转载自: 使用Guava retrying优雅的实现接口重调机制 Guava retrying:基于 guava 的重试组件 实际项目中,为了考虑网络抖动,加锁并发冲突等场景,我们经常需要对异常操作进 ...

  8. 使用Guava retryer优雅的实现接口重调机制

    API 接口调用异常, 网络异常在我们日常开发中经常会遇到,这种情况下我们需要先重试几次调用才能将其标识为错误并在确认错误之后发送异常提醒.guava-retry可以灵活的实现这一功能.Guava r ...

  9. 优雅对API进行内部升级改造

    优雅对API进行内部升级改造 背景 随着业务的快速发展老的系统将逐渐的无法快速支撑现有业务迭代重构一个必然的过程;然而在底层业务系统重构的过程中,对外提供的API也同时需要进行相应的升级替换;推动外部 ...

随机推荐

  1. golang开发:http请求redirect的问题

    这两天在开发项目的时候遇到了一个问题,请求了一个URL,它会302到另一个地址,本意上只是想检查这个URL是否会做3XX的redirect跳转,结果每次reqeust都会返回最后一跳的结果.后来就看了 ...

  2. 大二逃课总结的1.2w字的计算机网络知识!扫盲!

    本文是我在大二学习计算机网络期间整理, 大部分内容都来自于谢希仁老师的<计算机网络>这本书. 为了内容更容易理解,我对之前的整理进行了一波重构,并配上了一些相关的示意图便于理解. @ 目录 ...

  3. C# 编译器对局部变量的优化

    C# 编译器对局部变量的优化 C# 的编译器可以对代码进行优化,所以,我们在写代码的时候,可以更多地考虑一下代码的易读性问题. 不考虑基本的对齐和换行美化.看一下局部变量优化问题. C# 示例代码 例 ...

  4. 栈的C++实现

    数据结构c++实现系列第一篇. 话不多说,直接上代码. sichstack.h (头文件) 1 #pragma once 2 #include<string> 3 4 namespace ...

  5. 震惊!你还不知道SpringBoot真正的启动引导类

    引言 SpringBoot项目中的启动类,一般都是XXApplication,例如「StatsApplication」,「UnionApplication」. 每个项目的启动类名称都不一样.但是它的启 ...

  6. GXOI2018 滚粗记

    今天考了一次蜜汁省选,滚粗了.想了想,还是写点什么记录一下 8:10 折腾一番总算拿到题目和样例了,一打开dpf,立马感觉到了不对劲. 题目一股浓浓的劣质模拟题的画风,先不说题目质量,单是排版质量都被 ...

  7. IOCP三层结构

    iocp三层架构服务器模型 分类: GameEngine专题之IOCP(完成端口)2010-04-06 14:44 2611人阅读 评论(0) 收藏 举报 服务器socket工作测试应用服务器性能优化 ...

  8. Elasticsearch 第六篇:聚合统计查询

    h2.post_title { background-color: rgba(43, 102, 149, 1); color: rgba(255, 255, 255, 1); font-size: 1 ...

  9. C/C++四种取整函数floor,ceil,trunc,round

    处理浮点数操作常用到取整函数,C/C++提供了四种取整函数 floor函数 floor函数:向下取整函数,或称为向负无穷取整 double floor(double x); floor(-5.5) = ...

  10. 消息队列--ActiveMQ集群部署

    一.activeMQ主要的部署方式? 1,默认的单机部署(kahadb) activeMQ默认的存储单机模式,如果配置文件不做修改,则默认使用此模式.以本地的kahadb文件的方式进行存储,性能完全依 ...