博客 / 詳情

返回

SpringBoot定義優雅全局統一Restful API 響應框架六

閒話不多説,繼續優化 全局統一Restful API 響應框架 做到項目通用 接口可擴展。

如果沒有看前面幾篇文章請先看前面幾篇

SpringBoot定義優雅全局統一Restful API 響應框架

SpringBoot定義優雅全局統一Restful API 響應框架二

SpringBoot定義優雅全局統一Restful API 響應框架三

SpringBoot定義優雅全局統一Restful API 響應框架四

SpringBoot定義優雅全局統一Restful API 響應框架五

這裏講一講最後的版本和需要修復的一些問題

 @PostMapping("/add/UserApiCombo")
    public R addApiCombo(@RequestBody @Validated UserApplyApiComboDto userApplyApiComboDto) {
        userApiComboService.addApiCombo(userApplyApiComboDto);
        return R.success();
    }

我們看看這個代碼,有什麼問題。 我們返回了統一的封裝結果集R 但是後面所有的controller 都這麼寫不太友好。

  1. 返回內容這麼不夠明確具體
  2. 所有controller 這麼寫增加重複工作量

我們可以這麼去優化:

Spirng 提供了 ResponseBodyAdvice 接口,支持在消息轉換器執行轉換之前,對接口的返回結果進行處理,再結合 @ControllerAdvice 註解即可輕鬆支持上述功能

package cn.soboys.springbootrestfulapi.common.handler;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.MapUtil;
import cn.soboys.springbootrestfulapi.common.error.ErrorDetail;
import cn.soboys.springbootrestfulapi.common.resp.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
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.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * @author 公眾號 程序員三時
 * @version 1.0
 * @date 2023/6/12 12:17 下午
 * @webSite https://github.com/coder-amiao
 * @Slf4j
 * @ControllerAdvice
 */
@Slf4j
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
    /**
     * supports方法: 判斷是否要執行beforeBodyWrite方法,
     * true為執行,false不執行.
     * 通過該方法可以選擇哪些類或那些方法的response要進行處理, 其他的不進行處理.
     *
     * @param returnType
     * @param converterType
     * @return
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    /**
     * beforeBodyWrite方法: 對response方法進行具體操作處理
     * 實際返回結果業務包裝處理
     *
     * @param body
     * @param returnType
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof R) {
            return body;
        } else if (body == null) {
            return R.success();
        } else if (body instanceof ErrorDetail) {
            return body;
        } else if (body instanceof String) {
            return body;
        } else {
            return R.success().data(body);
        }
    }
}

在實際controller 返回中我們直接返回數據內容就可以了

    @GetMapping("/home")
    public Student home() {

        Student s = new Student();
        s.setUserName("Tom");
        s.setAge(22);
        List hobby = new ArrayList();
        hobby.add("抽煙");
        hobby.add("喝酒");
        hobby.add("燙頭");
        s.setHobby(hobby);
        s.setBalance(2229891.0892);
        s.setIdCard("420222199811207237");
        return s;
    }

我們目前版本中業務錯誤判斷邏輯不是很友好,還需要優化,這裏我們可以封裝自己的業務異常
Assert(斷言) 封裝異常,讓代碼更優雅

符合 錯誤優先返回原則

正常我們業務異常代碼是這樣寫的

// 另一種寫法
        Order order = orderDao.selectById(orderId);
        if (order == null) {
            throw new IllegalArgumentException("訂單不存在。");
        }

使用斷言優化後

 Order order = orderDao.selectById(orderId);
 Assert.notNull(order, "訂單不存在。");

兩種方式一對比,是不是明顯感覺第一種更優雅,第二種寫法則是相對醜陋的 if {...} 代碼塊。那麼 神奇的 Assert.notNull() 背後到底做了什麼呢?

這裏就是我們需要優化代碼

其實很多框架都帶有Assert 工具包括JAVA JDK . SpringBoot,spring 也有自己的Assert
但是不符合我們自己的異常拋出業務邏輯,這裏我們可以自定義自定的Assert 工具

我們來看一下部分源碼

public abstract class Assert {
    public Assert() {
    }

    public static void notNull(@Nullable Object object, String message) {
        if (object == null) {
            throw new IllegalArgumentException(message);
        }
    }
}

可以看到,Assert 其實就是幫我們把 if {...} 封裝了一下,是不是很神奇。雖然很簡單,但不可否認的是編碼體驗至少提升了一個檔次。

那麼我們是不是可以模仿Assert也寫一個自定義斷言類,不過斷言失敗後拋出的異常不是IllegalArgumentException 這些內置異常,而是我們自己定義的異常。

  1. 定義公共異常

    package cn.soboys.springbootrestfulapi.common.exception;
    
    import cn.soboys.springbootrestfulapi.common.resp.ResultCode;
    import lombok.Data;
    
    /**
     * @author 公眾號 程序員三時
     * @version 1.0
     * @date 2023/6/12 10:32 下午
     * @webSite https://github.com/coder-amiao
     */
    @Data
    public class BaseException extends RuntimeException {
     /**
      * 返回碼
      */
     protected ResultCode resultCode;
     /**
      * 異常消息參數
      */
     protected Object[] args;
    
     public BaseException(ResultCode resultCode) {
         super(resultCode.getMessage());
         this.resultCode = resultCode;
     }
    
    
     public BaseException(String code, String msg) {
         super(msg);
         this.resultCode = new ResultCode() {
             @Override
             public String getCode() {
                 return code;
             }
    
             @Override
             public String getMessage() {
                 return msg;
             }
    
             @Override
             public boolean getSuccess() {
                 return false;
             }
    
             ;
         };
     }
    
     public BaseException(ResultCode resultCode, Object[] args, String message) {
         super(message);
         this.resultCode = resultCode;
         this.args = args;
     }
    
     public BaseException(ResultCode resultCode, Object[] args, String message, Throwable cause) {
         super(message, cause);
         this.resultCode = resultCode;
         this.args = args;
     }
    
    }
  2. 所有其他異常繼承公共異常
package cn.soboys.springbootrestfulapi.common.exception;


import cn.soboys.springbootrestfulapi.common.resp.ResultCode;


/**
 * @author 公眾號 程序員三時
 * @version 1.0
 * @date 2023/4/29 00:15
 * @webSite https://github.com/coder-amiao
 * 通用業務異常封裝
 */
public class BusinessException extends BaseException {

    
    public BusinessException(ResultCode resultCode, Object[] args, String message) {
        super(resultCode, args, message);
    }

    public BusinessException(ResultCode resultCode, Object[] args, String message, Throwable cause) {
        super(resultCode, args, message, cause);
    }

}
  1. 斷言業務異常類封裝
public interface Assert {
    /**
     * 創建異常
     * @param args
     * @return
     */
    BaseException newException(Object... args);

    /**
     * 創建異常
     * @param t
     * @param args
     * @return
     */
    BaseException newException(Throwable t, Object... args);

    /**
     * <p>斷言對象<code>obj</code>非空。如果對象<code>obj</code>為空,則拋出異常
     *
     * @param obj 待判斷對象
     */
    default void assertNotNull(Object obj) {
        if (obj == null) {
            throw newException(obj);
        }
    }

    /**
     * <p>斷言對象<code>obj</code>非空。如果對象<code>obj</code>為空,則拋出異常
     * <p>異常信息<code>message</code>支持傳遞參數方式,避免在判斷之前進行字符串拼接操作
     *
     * @param obj 待判斷對象
     * @param args message佔位符對應的參數列表
     */
    default void assertNotNull(Object obj, Object... args) {
        if (obj == null) {
            throw newException(args);
        }
    }
}

具體使用

/**
     * 異常返回模擬
     *
     * @return
     */
    @GetMapping("/exception")
    public Student  exception() {
        Student s = null;
        BusinessErrorCode.Sign_Error.assertNotNull(s,"secret秘鑰不正確");
        return s;
    }

在業務中我們可以通過這個方式直接拋出枚舉異常。這樣代碼就簡潔乾淨很多

代理已經更新到 github倉庫腳手架項目

關注公眾號,程序員三時 持續輸出優質內容 希望給你帶來一點啓發和幫助

user avatar tian_study 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.