SpringBoot統一響應體解決方案

前言

最近在優化本身以前基於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的實現方案,須要知足如下幾點:後端

  1. 原有的基於AOP的實現方案須要Controller的返回類型爲Object,須要新方案不限制返回類型
  2. 原有的基於AOP的實現方案須要經過切面表達式+註解控制切點的Controller(註解的包名修改會致使切面表達式的修改,即須要修改兩處地方),須要新方案可以基於註解,而不須要修改切面表達式

方案思路

基於上述的需求,選擇使用SpringController加強機制,其中關鍵的類爲如下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;
    }
    
}

運行結果

  1. 在瀏覽器直接訪問http://127.0.0.1:8080/users/0,則返回結果以下(結果通過格式化處理):

    {
        "code": 10001,
        "msg": "資源不存在",
        "data": null
    }
  2. 在瀏覽器直接訪問http://127.0.0.1:8080/users/1,則返回結果以下(結果通過格式化處理):

    {
        "code": 50000,
        "msg": "服務器異常",
        "data": null
    }
  3. 在瀏覽器直接訪問http://127.0.0.1:8080/users/2,則返回結果以下(結果通過格式化處理):

    {
        "code": 10000,
        "msg": "success",
        "data": {
            "id": 2,
            "name": "test"
        }
    }

由運行結果能夠得知統一響應加強其實已經生效了,並且可以很好的處理異常。

示例代碼地址

下面是這個示例的代碼地址,若是以爲不錯或者幫助到你,但願你們給個Star
https://github.com/spring-bas...

參考資料

  1. https://docs.spring.io/spring...
  2. https://docs.spring.io/spring...
相關文章
相關標籤/搜索