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包裝一些錯誤資訊回傳,
總結
我們可以使用 @ControllerAdvice加 ResponseBodyAdvice 攔截回傳結果,統一做出一些修改,這樣就可以使用的業務代碼非常簡潔,優雅,
另外,針對業務代碼的中,我們可以使用 @ExceptionHandler注解,統一做一個全域例外處理,這樣就可以無縫的跟 ResponseBodyAdvice結合,
不過這里需要一點,我們實作的 ResponseBodyAdvice 類,一定需要跟 @ControllerAdvice配合一起使用哦,至于具體原因,下篇文章小黑哥分析原來的時候,再具體解釋哦,敬請期待哦~
歡迎關注我的公眾號:程式通事,獲得日常干貨推送,如果您對我的專題內容感興趣,也可以關注我的博客:studyidea.cn
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/227977.html
標籤:其他
