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

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

[[353770]]

 本文转载自微信公众号「Java极客技术」,作者鸭血粉丝 。转载本文请联系Java极客技术公众号。   

 Hello,早上好,我是阿粉~

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

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

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

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

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

  1.     "code""000000"
  2.     "msg""success"
  3.     "result": { 
  4.         "id""1"
  5.         "name""test" 
  6.     } 

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

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

重构之前

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

  1. /** 
  2.  * V1 版本 
  3.  * 
  4.  * @return 
  5.  */ 
  6. @RequestMapping("testv1"
  7. public APIResult testv1() { 
  8.     try { 
  9.         User user = new User(); 
  10.         user.setId("1"); 
  11.         user.setName("test"); 
  12.         return APIResult.success(user); 
  13.     } catch (APPException e) { 
  14.         log.error("内部异常", e); 
  15.         return APIResult.error(e.getCode(), e.getMsg()); 
  16.     } catch (Exception e) { 
  17.         log.error("系统异常", e); 
  18.         return APIResult.error(RetCodeEnum.FAILED); 
  19.     } 

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

  1. @Data 
  2. public class APIResult<T> implements Serializable { 
  3.  
  4.     private static final long serialVersionUID = 4747774542107711845L; 
  5.  
  6.     private String code; 
  7.  
  8.     private String msg; 
  9.  
  10.     private T result; 
  11.  
  12.  
  13.     public static <T> APIResult success(T result) { 
  14.         APIResult apiResult = new APIResult(); 
  15.         apiResult.setResult(result); 
  16.         apiResult.setCode("000000"); 
  17.         apiResult.setMsg("success"); 
  18.         return apiResult; 
  19.     } 
  20.  
  21.     public static APIResult error(String code, String msg) { 
  22.         APIResult apiResult = new APIResult(); 
  23.         apiResult.setCode(code); 
  24.         apiResult.setMsg(msg); 
  25.         return apiResult; 
  26.     } 
  27.  
  28.     public static APIResult error(RetCodeEnum codeEnum) { 
  29.         APIResult apiResult = new APIResult(); 
  30.         apiResult.setCode(codeEnum.getCode()); 
  31.         apiResult.setMsg(codeEnum.getMsg()); 
  32.         return apiResult; 
  33.     } 

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

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

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

重构之后

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

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

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

改写返回信息

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

  1. @ControllerAdvice 
  2. public class CustomResponseAdvice implements ResponseBodyAdvice { 
  3.     /** 
  4.      * 是否需要处理返回结果 
  5.      * @param methodParameter 
  6.      * @param aClass 
  7.      * @return 
  8.      */ 
  9.     @Override 
  10.     public boolean supports(MethodParameter methodParameter, Class aClass) { 
  11.         System.out.println("In supports() method of " + getClass().getSimpleName()); 
  12.         return true
  13.     } 
  14.  
  15.     /** 
  16.      * 处理返回结果 
  17.      * @param body 
  18.      * @param methodParameter 
  19.      * @param mediaType 
  20.      * @param aClass 
  21.      * @param serverHttpRequest 
  22.      * @param serverHttpResponse 
  23.      * @return 
  24.      */ 
  25.     @Override 
  26.     public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { 
  27.         System.out.println("In beforeBodyWrite() method of " + getClass().getSimpleName()); 
  28.         if (body instanceof APIResult) { 
  29.             return body; 
  30.         } 
  31.         return APIResult.success(body); 
  32.     } 

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

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

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

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

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

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

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

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

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

  1. /** 
  2.  * 是否需要处理返回结果 
  3.  * 
  4.  * @param methodParameter 
  5.  * @param aClass 
  6.  * @return 
  7.  */ 
  8. @Override 
  9. public boolean supports(MethodParameter methodParameter, Class aClass) { 
  10.     System.out.println("In supports() method of " + getClass().getSimpleName()); 
  11.     Method method = methodParameter.getMethod(); 
  12.     return method.isAnnotationPresent(CustomResponse.class); 

全局异常处理

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

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

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

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

  1. @Slf4j 
  2. @ControllerAdvice 
  3. public class CustomExceptionHandler { 
  4.  
  5.     @ExceptionHandler(Exception.class) 
  6.     @ResponseBody 
  7.     public APIResult handleException(Exception e) { 
  8.         log.error("系统异常", e); 
  9.         return APIResult.error(RetCodeEnum.FAILED); 
  10.     } 
  11.  
  12.     @ExceptionHandler(APPException.class) 
  13.     @ResponseBody 
  14.     public APIResult handleAPPException(APPException e) { 
  15.         log.error("内部异常", e); 
  16.         return APIResult.error(e.getCode(), e.getMsg()); 
  17.     } 
  18.  

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

总结

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

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

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

 

责任编辑:武晓燕 来源: Java极客技术
相关推荐

2021-07-06 21:37:05

索引SQL数据

2021-05-31 09:03:12

算法数据技术

2021-01-27 09:19:44

MySQL数据优化器

2020-12-07 08:04:39

CTO中年公司

2021-05-26 05:40:32

加密勒索软件攻击

2020-12-25 11:37:32

DDoS攻击信用卡黑客

2022-01-13 06:49:23

开源项目删库

2021-09-14 11:57:01

双重勒索勒索软件黑客攻击

2019-09-17 16:04:17

戴尔

2021-02-28 07:52:24

蠕虫数据金丝雀

2021-01-29 14:35:41

代码开发服务器

2020-11-05 14:48:29

AI人工智能互联网

2020-10-09 09:28:43

互联网数据技术

2019-04-01 15:28:20

996互联网ICU

2020-12-03 18:18:46

微信表情下回

2012-09-03 14:26:50

云计算亚马逊AWS

2020-05-20 15:37:43

VR虚拟现实年轻人

2010-08-20 10:34:29

施密特

2010-01-25 09:54:09

创业
点赞
收藏

51CTO技术栈公众号