使用Spring AOP 和自定義注解統一API返回值格式


摘要:統一接口返回值格式后,可以提高項目組前后端的產出比,降低溝通成本。因此,在借鑒前人處理方法的基礎上,通過分析資料,探索建立了一套使用Spring AOP和自定義注解無侵入式地統一返回數據格式的方法。

§前言

  我們封裝所有的Controller中接口返回結果,將其處理為統一返回數據結構后,可以提高前后端對接效率,降低溝通成本。而使用Spring AOP和自定義注解無侵入式地統一返回數據格式,則可以避免在每一個api上都處理返回格式,從而使后端開發節約開發時間,更加專注於開發業務邏輯。

  后端返回給前端的自定義返回格式如下:

{
    "code":200,
    "message":"OK",
    "data":{
    }
}

其中的三個參數的含義如下:

  • code: 返回狀態碼;
  • message: 返回信息的描述;
  • data: 返回值。

§直接修改API返回值類型

  Spring AOP和自定義注解的基本概念可以分別參考《Spring AOP 面向切面編程之AOP是什么》和《Spring注解之自定義注解入門》。下面定義返回數據格式的封裝類:

package com.eg.wiener.config.result;

import lombok.Getter;
import lombok.ToString;

import java.io.Serializable;

@Getter
@ToString
public class ResultData<T> implements Serializable {

    private static final long serialVersionUID = 4890803011331841425L;

    /** 業務錯誤碼 */
    private Integer code;
    /** 信息描述 */
    private String message;
    /** 返回參數 */
    private T data;

    private ResultData(ResultStatus resultStatus, T data) {
        this.code = resultStatus.getCode();
        this.message = resultStatus.getMessage();
        this.data = data;
    }

    /** 業務成功返回業務代碼和描述信息 */
    public static ResultData<Void> success() {
        return new ResultData<Void>(ResultStatus.SUCCESS, null);
    }

    /** 業務成功返回業務代碼,描述和返回的參數 */
    public static <T> ResultData<T> success(T data) {
        return new ResultData<T>(ResultStatus.SUCCESS, data);
    }

    /** 業務成功返回業務代碼,描述和返回的參數 */
    public static <T> ResultData<T> success(ResultStatus resultStatus, T data) {
        if (resultStatus == null) {
            return success(data);
        }
        return new ResultData<T>(resultStatus, data);
    }

    /** 業務異常返回業務代碼和描述信息 */
    public static <T> ResultData<T> failure() {
        return new ResultData<T>(ResultStatus.INTERNAL_SERVER_ERROR, null);
    }

    /** 業務異常返回業務代碼,描述和返回的參數 */
    public static <T> ResultData<T> failure(ResultStatus resultStatus) {
        return failure(resultStatus, null);
    }

    /** 業務異常返回業務代碼,描述和返回的參數 */
    public static <T> ResultData<T> failure(ResultStatus resultStatus, T data) {
        if (resultStatus == null) {
            return new ResultData<T>(ResultStatus.INTERNAL_SERVER_ERROR, null);
        }
        return new ResultData<T>(resultStatus, data);
    }
}

 === 我是分割線 === 
 package com.eg.wiener.config.result;

import lombok.Getter;
import lombok.ToString;
import org.springframework.http.HttpStatus;

@ToString
@Getter
public enum ResultStatus {
    SUCCESS(HttpStatus.OK.value(), "OK"),
    BAD_REQUEST(HttpStatus.BAD_REQUEST.value(), "Bad Request"),
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal Server Error"),
    TOO_MANY_REQUESTS(HttpStatus.TOO_MANY_REQUESTS.value(), "請勿重復請求"),
    USER_NOT_FIND(-99, "請登陸");

    /**
     * 業務狀態碼
     */
    private Integer code;
    /**
     * 業務信息描述
     */
    private String message;

    ResultStatus(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

  在UserController類中添加測試函數,其返回值直接使用上面定義的ResultData:

/**
 * @author Wiener
 */
@RestController
@RequestMapping("/user")
public class UserController {
    private static Logger logger = LoggerFactory.getLogger(UserController.class);

    /**
     * 示例地址 xxx/user/getResultById?userId=1090330
     *
     * @author Wiener
     */ 
    @GetMapping(value ="/getResultById", produces = "application/json; charset=utf-8")
    public ResultData getResultById(Long userId) {
        User user = new User();
        user.setId(userId);
        user.setAddress("測試地址是 " + UUID.randomUUID().toString());
        logger.info(user.toString());
        return ResultData.success(user);
    }
}

  啟動項目,在瀏覽器中輸入URL,得到如下結果:

{
    "code":200,
    "message":"OK",
    "data":{
        "id":1090330,
        "userName":null,
        "age":null,
        "address":"測試地址是 f057181c-e7e2-41ec-9066-0e72619f0f86",
        "mobilePhone":null
    }
}

  說明已經成功定義返回數據格式,但是,這里並沒有使用自定義注解和Spring AOP這兩個知識點。客官莫急,待我喝完這杯咖啡,且聽我娓娓道來。

§設置全局返回值類型

  下面首先定義一個用於設置全局切入點的自定義注解@ResponseResultBody。參考文獻中強調,此自定義注解需要添加@ResponseBody注解,這是畫蛇添足,下文的測試用例將給大家演示不添加也可以達到同樣的目的。

package com.eg.wiener.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ResponseResultBody {
}

  通過實現ResponseBodyAdvice接口,基於上述自定義注解@ResponseResultBody獲取切面,封裝返回值格式:

package com.eg.wiener.config.result;

import com.eg.wiener.aop.ResponseResultBody;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.lang.annotation.Annotation;

@RestControllerAdvice
public class ResultBodyAdvice implements ResponseBodyAdvice<Object> {

    private static final Class<? extends Annotation> ANNOTATION_TYPE = ResponseResultBody.class;

    /**
     * 基於自定義注解判斷類或者方法是否使用了注解 @ResponseResultBody,使用就攔截
     * @return boolean. true:執行函數beforeBodyWrite;否則,不執行
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE)
                || returnType.hasMethodAnnotation(ANNOTATION_TYPE);
    }

    /**
     * 當類或者方法使用了 @ResponseResultBody 就會調用這個方法
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
          Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 避免重復封裝響應報文
        if (body instanceof ResultData) {
            return body;
        }
        // 使用約定全局返回格式封裝響應報文
        return ResultData.success(body);
    }

}

  修改測試用例,添加自定義注解@ResponseResultBody:


/**
 * @author Wiener
 */
@RestController
@RequestMapping("/user")
@ResponseResultBody
public class UserController {
    private static Logger logger = LoggerFactory.getLogger(UserController.class);

     * 示例地址 /user/getUserTestById?userId=1090330
     *
     * @author Wiener
     */
    @GetMapping(value ="/getUserTestById", produces = "application/json; charset=utf-8")
    public User getUserTestById(Long userId) throws Exception {
        User user = new User();
        user.setId(userId);
        user.setAddress("測試地址是 " + UUID.randomUUID().toString());
        logger.info(user.toString());
        return user;
    }

    /**
     * /user/getResult?userId=1090330
     * @param user
     * @return
     */
    @PostMapping(value ="/getResult", produces = "application/json; charset=utf-8")
    public ResultData getResult(@RequestBody User user) {
        user.setAddress("測試地址是 " + UUID.randomUUID().toString());
        logger.info(user.toString());
        return ResultData.success(user);
    }
}

  啟動服務,在Postman中請求getResult函數,可以得到如下結果:

在瀏覽器中請求API getUserTestById,可以得到如下結果:

說明成功統一返回值格式。

§小結

  本篇文章的內容基本上就是這些,我們來總結一下。在控制層添加自定義注解@ResponseResultBody后,我們就可以通過Spring AOP定義基於此注解的切面。如果一個代理類使用了此注解,那么就封裝其返回值;否則,不處理。

§Reference


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM