統一響應結果和全局異常處理
- 前言
- 統一響應結果
- 定義響應結構
- 定義響應對象
- 定義響應狀態對象
- 統一返回響應對象
- 定義 Controller 攔截類
- 全局異常處理
前言
做個功能之前我們想一下為什么要做統一響應結果和全局異常處理呢?
這是因為我們的項目采用的是前后端分離開發,并且使用 Restful 風格的接口形式,返回的業務數據以及異常都是以 JSON 的格式返回,但是目前我們返回的數據格式都是不一致的,比如如下的代碼:
@GetMapping("/{id}")
@Operation(summary = "根據用戶ID查詢用戶")
public UserEntity get(@PathVariable Long id) {return userService.get(id);
}@GetMapping("/list")
@Operation(summary = "查詢全部")
public List<UserEntity> lists() {return userService.lists();
}@GetMapping("/page")
@Operation(summary = "分頁查詢")
public Page<UserEntity> page(int page, int size) {return userService.page(PageRequest.of(page - 1, size));
}@PostMapping
@Operation(summary = "新增用戶")
public void save(@RequestBody UserEntity user) {userService.save(user);
}
由上面的接口返回值可以看出,我們4個接口就有4中返回數據格式,這時前端工作人員在對接的時候就接口她們就會很難受,需要寫很多的處理來處理不同的返回數據,非常的不方便,而且我們自己使用起來也不好管理,所以在實際開發中,為了降低開發人員之間的溝通成本,一般返回結果會定義成一個統一格式,具體的格式根據實際開發業務不同有所區別。
統一響應結果
定義響應結構
從前端的角度思考,調用后端接口后如果成功了則拿到想要的數據或者對應的信息提示,如果不成功也應該有相應的信息提示。從這個角度出發,我們將響應結果設計為:
code
:響應狀態碼,根據狀態碼判斷是否成功
message
:提示信息,根據調用的接口自定義
body
:返回的數據
響應的結構如下:
{"code": 200,"message": "成功","body": [{"createUser": "admin","updateUser": "admin","createTime": "2023-12-09 14:14:20","updateTime": "2023-12-09 14:14:20","id": 2,"name": "金克斯","nickname": "爆爆","age": 18,"email": null,"password": "666"}]
}
定義響應對象
Spring 官方源碼里面也有一個類似的對象,類名為 ResponseEntity
,如果你想直接用這個類也行的,但是這個類我使用起來有點不合我的習慣,所以還是自定義一個好點。
@Data
public class Response<T> implements Serializable {@Schema(description = "響應狀態")private Integer code;@Schema(description = "響應信息")private String message;@Schema(description = "響應數據")private T body;/*** 私有化構造函數*/private Response() {}/*** 構建 Response<T> 對象** @param body 響應數據* @param code 響應狀態對象*/public static <T> Response<T> build(@Nullable T body, ResponseCode code) {return build(body, code.getCode(), code.getMessage());}/*** 構建 Response<T> 對象** @param body 響應數據* @param code 響應狀態* @param message 響應信息*/public static <T> Response<T> build(@Nullable T body, Integer code, String message) {Response<T> response = new Response<>();response.setCode(code);response.setMessage(message);response.setBody(body);return response;}/*** 請求成功返回不帶數據*/public static <T> Response<T> ok() {return build(null, ResponseCode.OK);}/*** 請求成功返回帶數據*/public static <T> Response<T> ok(T body) {return build(body, ResponseCode.OK);}/*** 請求失敗返回500*/public static <T> Response<T> error() {return build(null, ResponseCode.ERROR);}/*** 請求失敗狀態碼*/public static <T> Response<T> error(ResponseCode code) {return build(null, code);}/*** 自定義請求失敗信息*/public static <T> Response<T> error(Integer code, String message) {return build(null, code, message);}}
定義響應狀態對象
public enum ResponseCode {/*** 成功*/OK(200, "OK"),/*** 失敗*/ERROR(500,"系統異常,請稍后重試");private final int code;private final String message;ResponseCode(int code, String message) {this.code = code;this.message = message;}public int getCode() {return code;}public String getMessage() {return message;}
}
統一返回響應對象
這里有兩種方式可以做到全局返回我們定義好的 Response
對象,第一種就是在定義方法的時候返回值都定義為 Resopnse
對象,這是可以完成的,但是我們還有更高級一點的用法,使用 Spring ResponseBodyAdvice
類即可,ResponseBodyAdvice的作用就是攔截Controller方法的返回值,統一處理返回值/響應體,一般用來統一返回格式,加解密,簽名等等。
我們這里采用第二種方式來完成統一響應結果。
定義 Controller 攔截類
@RestControllerAdvice(basePackages = {"com.xm.module"})
public class ResponseAdviceHandler implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {// 響應都需要被攔截return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof String) {// String 類型不能封裝成 Response 返回return JSONUtil.toJsonStr(Response.ok(body));} else if (body instanceof Response) {// 如果是 Response 就直接返回了return body;} else {// 統一響應對象return Response.ok(body);}}
}
@RestControllerAdvice
是@RestController
注解的增強,可以實現三個方面的功能:
- 全局異常處理
- 全局數據綁定
- 全局數據預處理
basePackages
表示是只攔截 com.xm.module 下的響應
JSONUtil.toJsonStr(Response.ok(body));
這個 JSONUtil 我是用的 hutool 工具包里面的,好用滴很。有需要的自己導入。Hutool 官網
啟動程序測試一下
這里報了一個異常,而且我們發現異常信息的響應結果和我們預想的不一致,這個我們在下面全局異常攔截的時候在進行處理,先把這個接口異常給解決一下。
異常的大致意思是不能初始化代理,沒有 session,這是因為我們使用了懶加載的方法,修改一下這個接口方法。
@Override
public UserEntity get(Long id) {return userRepository.findById(id).orElse(null);
}
ok,接口請求成功,并且響應的結構和我們預想的一致,nice。
全局異常處理
剛才我們看到,在接口拋出異常的時候,我們的響應結構和我們預想的不一致,我們希望在拋出異常的時候也能返回我們的 Response
對象,怎么實現呢,還是老辦法,上面我們提到了 @RestControllerAdvice
是 @RestController
注解的增強,可以實現全局異常處理,那我們就在使用這個注解進行全局異常的攔截處理吧。
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 全局異常處理。* ExceptionHandler(Exception.class) 表示攔截 Exception 異常,這是所有異常都攔截* 我們還可以配置 ExceptionHandler(XXException.class) 等等進行更加精細的攔截操作** @param e 異常* @return Response*/@ExceptionHandler(Exception.class)public Response<String> exception(Exception e) {return Response.error(ResponseCode.ERROR);}}
編寫好了這個之后,我們再來測試一下異常的情況下返回值是什么樣的,
ok,異常攔截成功,關于更多的異常,可以自己定義攔截即可,本節內容就到這里了,bye~