最近在優化本身以前基於Spring AOP
的統一響應體的實現方案。html
什麼是統一響應體呢?在目前的先後端分離架構下,後端主要是一個RESTful API
的數據接口。java
可是HTTP
的狀態碼數量有限,而隨着業務的增加,HTTP
狀態碼沒法很好地表示業務中遇到的異常狀況。git
那麼能夠經過修改響應返回的JSON
數據,讓其帶上一些固有的字段,例如如下這樣的github
{ "code": 10000, "msg": "success", "data": { "id": 2, "name": "test" } }
其中關鍵屬性的用途以下:web
code
爲返回結果的狀態碼msg
爲返回結果的消息data
爲返回的業務數據這3
個屬性爲固有屬性,每次響應結果都會有帶有它們。spring
但願實現一個可以代替基於AOP
的實現方案,須要知足如下幾點:後端
AOP
的實現方案須要Controller
的返回類型爲Object
,須要新方案不限制返回類型AOP
的實現方案須要經過切面表達式+註解控制切點的Controller
(註解的包名修改會致使切面表達式的修改,即須要修改兩處地方),須要新方案可以基於註解,而不須要修改切面表達式基於上述的需求,選擇使用Spring
的Controller
加強機制,其中關鍵的類爲如下3
個:瀏覽器
@ControllerAdvice
:類註解,用於指定Controller
加強處理器類。ResponseBodyAdvice
:接口,實現後beforeBodyWrite()
方法後能夠對響應的body
進行修改,須要結合@ControllerAdvice
使用。@ExceptionHandler
:方法註解,用於指定異常處理方法,須要結合@ControllerAdvice
和@ResponseBody
使用。本示例使用的Spring Boot
版本爲2.1.6.RELEASE
,同時須要開發工具安裝lombok
插件服務器
<dependencies> <!--web-starter--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--test-starter--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
Controller
加強後統一響應體對應的對象架構
import lombok.AllArgsConstructor; import lombok.Data; import java.io.Serializable; /** * 統一的公共響應體 * @author NULL * @date 2019-07-16 */ @Data @AllArgsConstructor public class ResponseResult implements Serializable { /** * 返回狀態碼 */ private Integer code; /** * 返回信息 */ private String msg; /** * 數據 */ private Object data; }
統一響應註解是一個標記是否開啓統一響應加強的註解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 統一響應註解<br/> * 添加註解後,統一響應體才能生效 * @author NULL * @date 2019-07-16 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface BaseResponse { }
統一響應體中返回的狀態碼code
和狀態信息msg
對應的枚舉類
/** * 返回狀態碼 * * @author NULL * @date 2019-07-16 */ public enum ResponseCode { /** * 成功返回的狀態碼 */ SUCCESS(10000, "success"), /** * 資源不存在的狀態碼 */ RESOURCES_NOT_EXIST(10001, "資源不存在"), /** * 全部沒法識別的異常默認的返回狀態碼 */ SERVICE_ERROR(50000, "服務器異常"); /** * 狀態碼 */ private int code; /** * 返回信息 */ private String msg; ResponseCode(int code, String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public String getMsg() { return msg; } }
業務異常類是用於識別業務相關的異常,須要注意這個異常類強制須要以ResponseCode
做爲構造方法入參,這樣能夠經過捕獲異常得到返回的狀態碼信息
import com.rjh.web.response.ResponseCode; import lombok.Data; import lombok.EqualsAndHashCode; /** * 業務異常類,繼承運行時異常,確保事務正常回滾 * * @author NULL * @since 2019-07-16 */ @Data @EqualsAndHashCode(callSuper = false) public class BaseException extends RuntimeException{ private ResponseCode code; public BaseException(ResponseCode code) { this.code = code; } public BaseException(Throwable cause, ResponseCode code) { super(cause); this.code = code; } }
用於處理Controller
運行時未捕獲的異常的處理類。
import com.rjh.web.exception.BaseException; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * 異常處理器 * * @author NULL * @since 2019-07-16 */ @ControllerAdvice(annotations = BaseResponse.class) @ResponseBody @Slf4j public class ExceptionHandlerAdvice { /** * 處理未捕獲的Exception * @param e 異常 * @return 統一響應體 */ @ExceptionHandler(Exception.class) public ResponseResult handleException(Exception e){ log.error(e.getMessage(),e); return new ResponseResult(ResponseCode.SERVICE_ERROR.getCode(),ResponseCode.SERVICE_ERROR.getMsg(),null); } /** * 處理未捕獲的RuntimeException * @param e 運行時異常 * @return 統一響應體 */ @ExceptionHandler(RuntimeException.class) public ResponseResult handleRuntimeException(RuntimeException e){ log.error(e.getMessage(),e); return new ResponseResult(ResponseCode.SERVICE_ERROR.getCode(),ResponseCode.SERVICE_ERROR.getMsg(),null); } /** * 處理業務異常BaseException * @param e 業務異常 * @return 統一響應體 */ @ExceptionHandler(BaseException.class) public ResponseResult handleBaseException(BaseException e){ log.error(e.getMessage(),e); ResponseCode code=e.getCode(); return new ResponseResult(code.getCode(),code.getMsg(),null); } }
Conrtoller
加強的統一響應體處理類,須要注意異常處理類已經進行了加強,因此須要判斷一下返回的對象是否爲統一響應體對象。
import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; /** * 統一響應體處理器 * @author NULL * @date 2019-07-16 */ @ControllerAdvice(annotations = BaseResponse.class) @Slf4j public class ResponseResultHandlerAdvice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class converterType) { log.info("returnType:"+returnType); log.info("converterType:"+converterType); return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if(MediaType.APPLICATION_JSON.equals(selectedContentType) || MediaType.APPLICATION_JSON_UTF8.equals(selectedContentType)){ // 判斷響應的Content-Type爲JSON格式的body if(body instanceof ResponseResult){ // 若是響應返回的對象爲統一響應體,則直接返回body return body; }else{ // 只有正常返回的結果纔會進入這個判斷流程,因此返回正常成功的狀態碼 ResponseResult responseResult =new ResponseResult(ResponseCode.SUCCESS.getCode(),ResponseCode.SUCCESS.getMsg(),body); return responseResult; } } // 非JSON格式body直接返回便可 return body; } }
首先準備一個User
對象
import lombok.Data; import lombok.EqualsAndHashCode; import java.io.Serializable; /** * 用戶類 * @author NULL * @date 2019-07-16 */ @Data @EqualsAndHashCode public class User implements Serializable { private Integer id; private String name; }
而後是準備一個簡單的UserController
便可
import com.rjh.web.entity.User; import com.rjh.web.exception.BaseException; import com.rjh.web.response.BaseResponse; import com.rjh.web.response.ResponseCode; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 測試用的Controller * * @author NULL * @date 2019-07-16 */ @BaseResponse @RestController @RequestMapping("users") public class UserController { @GetMapping("/{userId}") public User getUserById(@PathVariable Integer userId){ if(userId.equals(0)){ throw new BaseException(ResponseCode.RESOURCES_NOT_EXIST); } if(userId.equals(1)){ throw new RuntimeException(); } User user=new User(); user.setId(userId); user.setName("test"); return user; } }
在瀏覽器直接訪問http://127.0.0.1:8080/users/0
,則返回結果以下(結果通過格式化處理):
{ "code": 10001, "msg": "資源不存在", "data": null }
在瀏覽器直接訪問http://127.0.0.1:8080/users/1
,則返回結果以下(結果通過格式化處理):
{ "code": 50000, "msg": "服務器異常", "data": null }
在瀏覽器直接訪問http://127.0.0.1:8080/users/2
,則返回結果以下(結果通過格式化處理):
{ "code": 10000, "msg": "success", "data": { "id": 2, "name": "test" } }
由運行結果能夠得知統一響應加強其實已經生效了,並且可以很好的處理異常。
下面是這個示例的代碼地址,若是以爲不錯或者幫助到你,但願你們給個Star
:
https://github.com/spring-bas...