閒話不多説,繼續優化 全局統一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 都這麼寫不太友好。
- 返回內容這麼不夠明確具體
- 所有
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 這些內置異常,而是我們自己定義的異常。
-
定義公共異常
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; } } - 所有其他異常繼承公共異常
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);
}
}
- 斷言業務異常類封裝
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倉庫腳手架項目
關注公眾號,程序員三時 持續輸出優質內容 希望給你帶來一點啓發和幫助