当前位置: 首页 > 科技观察

年轻人不谈武功,居然重构了这么优雅的后台API接口

时间:2023-03-21 13:00:08 科技观察

本文转载请联系Java极客技术公众号。你好,早上好,我是粉~最近无意中看到了Spring官方文档,学到了一个新的注解@ControllerAdvice,并成功利用这个注解重构了我们项目的对外API接口,去掉了繁琐的重复代码.使其开发更加优雅。在展示具体的重构代码之前,我们先来看看原来的对外API接口是如何开发的。这个API接口主要是用来和我们的APP进行交互的。在这个过程中,我们统一定义了一个交互协议。APP端和后台API接口均使用JSON格式。另外,后台API接口返回APP时,统一了一些错误码,APP端需要根据相应的错误码在页面弹出一些提示。下面是一个查询用户信息返回的接口数据:{"code":"000000","msg":"success","re??sult":{"id":"1","name":"test"}}code表示外部错误码,msg表示错误信息,result表示具体返回信息。前端APP在获取返回信息时,首先判断接口返回的code是否为“000000”。如果是,则表示查询成功,然后获取结果信息进行相应显示。否则会直接弹出相应的错误信息。在重构之前,我们先看看重构前如何对后台API层进行编码。/***V1版本**@return*/@RequestMapping("testv1")publicAPIResulttestv1(){try{Useruser=newUser();user.setId("1");user.setName("test");returnAPIResult.success(user);}catch(APPExceptione){log.error("内部异常",e);returnAPIResult.error(e.getCode(),e.getMsg());}catch(Exceptione){log.error("系统异常",e);returnAPIResult.error(RetCodeEnum.FAILED);}}上面的代码其实很简单,内部封装了一个工具类APIResult,然后用它来封装具体的结果。@DatapublicclassAPIResultimplementsSerializable{privatestaticfinallongserialVersionUID=4747774542107711845L;privateStringcode;privateStringmsg;privateTresult;publicstaticAPIResultsuccess(Tresult){APIResultapiResult=newAPIResult();apiResult.set0.CodeResult0.api0"0"api""setMscess")g(;returnapiResult;}publicstaticAPIResulterror(Stringcode,Stringmsg){APIResultapiResult=newAPIResult();apiResult.setCode(code);apiResult.setMsg(msg);returnapiResult;}publicstaticAPIResulterror(RetCodeEnumcodeEnum){APIResultapiResult=newAPIResult();apiResult.setCode(codeEnum.getCode());apiResult.setMsg(codeEnum.getMsg());returnapiResult;}除此之外,它还定义了一个异常对象APPException,用来统一封装内部各种异常,上面的代码很简单,但是可以说是繁琐,重复的代码很多,每个接口都需要用try...catch包裹起来,然后用APIResult来包含正常的返回信息和errorinf形成。第二,接口对象只能返回APIResult,真正的业务对象只能隐藏在APIResult中。这样不是很优雅,也不是很直观的知道真正的业务对象。重构完之后,我们开始重构上面的代码,主要目的是去掉重复的try...catch代码。对于这个重构,我们需要使用Spring注解@ControllerAdvice和ResponseBodyAdvice。我们先来看看重构后的代码。ps:ResponseBodyAdvice来自Spring4.2API。如果需要使用这个,可能需要升级Spring版本。要改写返回信息,首先我们需要实现ResponseBodyAdvice,实现自己的处理类。@ControllerAdvicepublicclassCustomResponseAdviceimplementsResponseBodyAdvice{/***是否需要处理返回结果*@parammethodParameter*@paramaClass*@return*/@Overridepublicbooleansupports(MethodParametermethodParameter,ClassaClass){System.out.println("Insupports()methodof"+getSimpleName().getSimpleName()));returntrue;}/***处理返回结果*@parambody*@parammethodParameter*@parammediaType*@paramaClass*@paramserverHttpRequest*@paramserverHttpResponse*@return*/@OverridepublicObjectbeforeBodyWrite(Objectbody,MethodParametermethodParameter,MediaTypemediaType,ClassaClass,ServerHttpRequestserverHttpRequest,ServerHttpResponseserverHttpResponse){System.out.println("InbeforeBodyWrite()methodof"+getClass().getSimpleName());if(bodyinstanceofAPIResult){returnbody;}returnAPIResult.success(body);}}实现以上接口,我们可以在beforeBodyWrite方法中,修改返回的结果。在上面的代码中,返回的结果只是简单地用APIResult包裹并返回。其实我们也可以在这里添加一些额外的逻辑。比如接口返回的信息是加密的,我们可以在这一层统一加密。另外这里检查body是否是APIResult类,如果是则直接返回,不做任何修改。这样做是为了兼容之前的旧接口,因为默认情况下,我们实现的CustomResponseAdvice类会对所有Controller生效。如果不判断,之前的老连接会被APIResul包裹两层,影响APP解析。另外,如果担心这个修改会影响到之前的旧界面,可以使用下面的方法只对指定的方法生效。先自定义一个注解,例如:@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceCustomResponse{}然后在需要改变的方法中标记,然后我们在ResponseBodyAdvice#supports中,判断具体方法上是否有自定义注解CustomResponse。如果存在则返回true,表示最后会修改返回类。如果不存在,则返回false,则流程同上。/***是否需要处理返回结果**@parammethodParameter*@paramaClass*@return*/@Overridepublicbooleansupports(MethodParametermethodParameter,ClassaClass){System.out.println("Insupports()methodof"+getClass().getSimpleName());Methodmethod=methodParameter.getMethod();returnmethod.isAnnotationPresent(CustomResponse.class);}全局异常处理上面的代码重构后,把重复的代码抽取出来,整体代码就剩下我们的业务逻辑,变得非常简洁大方。但是上面重构的代码还是有问题,主要是在异常处理上。如果上述业务代码抛出异常,接口将返回堆栈错误信息,而不是我们定义的错误信息。所以让我们再次优化这个。这次我们主要需要用到@ExceptionHandler注解,这个注解需要和@ControllerAdvice配合使用。@Slf4j@ControllerAdvicepublicclassCustomExceptionHandler{@ExceptionHandler(Exception.class)@ResponseBodypublicAPIResulthandleException(Exceptione){log.error("系统异常",e);returnAPIResult.error(RetCodeEnum.FAILED);}@ExceptionHandler(APPExceptiononAPIhBothAPPExceptione){log.error("内部异常",e);returnAPIResult.error(e.getCode(),e.getMsg());}}使用这个@ExceptionHandler会拦截对应的异常,然后调用对应的方法处理异常。这里我们使用APIResult将一些错误信息包装回来。综上所述,我们可以使用@ControllerAdvice加上ResponseBodyAdvice来拦截返回的结果,统一做一些修改。这样一来,可以使用的业务代码就非常简洁优雅了。另外,对于业务代码,我们可以使用@ExceptionHandler注解统一做一个全局的异常处理,这样可以和ResponseBodyAdvice无缝结合。但是这里有一点,我们实现的ResponseBodyAdvice类必须要和@ControllerAdvice配合使用。至于具体原因,我会在下一篇分析原文的时候详细说明。敬请期待~