1、項目地址
工具類-commons【Git地址 https://gitee.com/ying_kevin/commons】
2、項目簡介
本項目構建目的主要是致力於開發一套適用於微服務架構體系的公共類封裝,解決各微服務間的代碼重復利用,規范微服務項目中API接口規范,約定代碼規范性。項目的完善是一個不斷迭代更新的過程,同時也歡迎各位小伙伴的加入,共同完善本項目。
3、項目結構
項目包含以下部分:
commons-api:封裝API接口報文、統一異常處理、基本字段填充、分布式唯一id生產
commons-core:封裝公共的工具類
4、commons-api功能列表
- 統一REST ful風格API接口報文
- 基本字段填充
- 全局異常處理
- 分布式唯一主鍵生成工具類
5、commons-core功能列表
- AsyncUtil 異步工具類
- IDCardCheckUtil 身份證合法性校驗工具類
6、springBoot項目中接入全局異常處理
commons-api中已經對全局異常進行了處理,默認的統一業務異常為自定義的ServiceException,以及實現了參數校驗異常的全局捕獲處理,對應的異常信息如下:
org.springframework.validation.BindException:參數綁定異常,例: 參數名錯誤或未傳參數
org.springframework.web.bind.MethodArgumentNotValidException:Bean 校驗異常,例:不符合添加注解要求,NotNull等
javax.validation.ConstraintViolationException:方法參數校驗異常(違反約束,javax擴展定義),例: 如實體類中的@Size注解配置和數據庫中該字段的長度不統一等問題
com.kevin.commons.api.exception.ServiceException:自定義業務異常
如果拋出的異常不是以上列出的,則統一拋出系統異常。
6.1、接入全局異常測試
那么在項目中怎么接入commons-api中全局異常?只用創建一個類去繼承GlobalExceptionHandler,添加RestControllerAdvice注解
@Order(Integer.MAX_VALUE - 1)
@RestControllerAdvice
public class GlobalException extends GlobalExceptionHandler {
}
接下來,我們寫一個接口測試全局異常處理是否成功接入。調用的接口如下:
@GetMapping("/getCategoryName")
ResultTemplate<CategoryRes> getCategoryName(@RequestParam("parent_id") Long parentId);
在接口實現中,如果傳入的參數為空或者為0,則拋出一個異常
@Override
public List<CategoryRes> getCategoryName(Long parentId) {
if(ObjectUtils.isEmpty(parentId)){
throw new ServiceException("傳入的參數不能為空");
}
List<CategoryRes> categoryResList = tabCategoryMapper.selectByParentId(parentId);
return categoryResList;
}
調用接口,可以看到ServiceException異常已經被捕獲到了。
6.2、新增自定義異常
那么,是否可以對項目中某個異常進行特殊處理?當然可以,接下來就舉例說明一下。如果我的項目中需要對DuplicateKeyException(mysql插入數據主鍵沖突)進行捕獲,而不是直接顯示系統錯誤,那需要怎么處理?我們接着上面的接口實現方法,在代碼中添加了插入數據的代碼,並且指定id為5,該id值在數據庫中已經存在。
@Override
public List<CategoryRes> getCategoryName(Long parentId) {
if(ObjectUtils.isEmpty(parentId) || parentId.equals(0L)){
throw new ServiceException("傳入的參數不能為空");
}
TabCategory tabCategory=new TabCategory();
tabCategory.setCategoryId(5L);
tabCategoryMapper.insertSelective(tabCategory);
List<CategoryRes> categoryResList = tabCategoryMapper.selectByParentId(parentId);
return categoryResList;
}
調用接口,從日志信息可以看出拋出的異常為DuplicateKeyException,該異常我們沒有進行全局捕獲,也沒有針對上面插入數據代碼進行異常捕獲,所以提示信息為系統異常:
如果我們想把該異常信息提示為主鍵沖突,那么我們可以在插入數據代碼中進行異常捕獲,但這樣操作,每次插入數據都有捕獲,代碼冗余。所以,我們需要進行全局捕獲。
commons-api中全局異常捕獲,使用的是責任鏈模式進行的封裝,在我們拋出一個異常后,不會再繼續調用后面異常的業務邏輯。當然,對於全局異常捕獲封裝的方法不是很完美,新增的時候操作太繁瑣,后期我會抽出時間繼續優化該方法。下面,我們從以下案例中進行學習一下,如何新增一個我們需要捕獲的異常。
1.新建一個類繼承AbstractOtherException,重寫setOtherException方法,在該方法中添加需要捕獲的異常全稱,注意:key值從var4開始,示例如下:
public class CustomException extends AbstractOtherException {
@Override
protected Map<String, Object> setOtherException() {
System.out.println(exceptionMaps.size());
exceptionMaps.put("var4", "org.springframework.dao.DuplicateKeyException");
return exceptionMaps;
}
}
2.新建一個類繼承BaseHandler並實現ExceptHandler,重寫handler方法
public class DuplicateKeyExceptionHandler extends BaseHandler implements ExceptHandler {
@Override
public Map<String, Object> handler(HttpServletRequest request, HttpServletResponse response, Throwable throwable) {
if (throwable instanceof DuplicateKeyException) {
String subCode = ServiceErrorEnum.ISV_COMMON_ERROR.getSubCode();
String subMsg = "主鍵錯誤";
return result(subCode, subMsg);
}
return nextHandler.handler(request, response, throwable);
}
@Override
public ExceptHandler getNextHandler() {
return null;
}
}
3.因為默認的DefaultFactoryHandler中沒有對該異常進行調用,所以要實現FactoryHandler,重寫該方法。handler1、handler2、handler3分別負責數據綁定異常的掉一個呢,
handler4負責自定義serviceException異常的調用、handlerLast負責對未定義異常的調用,其中的1、2...等數字代表的是調用順序,比如:拋出handler1的異常就不會繼續走后面異常的業務邏輯。
那么,我們添加的異常就放在handlerLast前面。
public class CustomFactoryHandler implements FactoryHandler {
@Override
public ExceptHandler getOneHandler() {
ExceptHandler handler1 = ApiContext.getApiConfig().getBindExceptionHandler();
ExceptHandler handler2 = ApiContext.getApiConfig().getMethodArgumentNotValidExceptionHandler();
handler1.setNextHandler(handler2);
ExceptHandler handler3 = ApiContext.getApiConfig().getConstraintViolationExceptionHandler();
handler2.setNextHandler(handler3);
ExceptHandler handler4 = ApiContext.getApiConfig().getServiceExceptionHandler();
handler3.setNextHandler(handler4);
// 新增異常 start
ExceptHandler handler5 = new DuplicateKeyExceptionHandler();
handler4.setNextHandler(handler5);
// end
ExceptHandler handlerLast = ApiContext.getApiConfig().getLastExceptionHandler();
handler5.setNextHandler(handlerLast);
return handler1;
}
}
3.以上方法重寫完成后我們需要在項目啟動的時候就加載,我們只用新建一個類繼承BaseConfiguration,進行覆蓋調用。
@Configuration
public class WeixinConfig extends BaseConfiguration {
static {
ApiContext.getApiConfig().setFactoryHandler(new CustomFactoryHandler());
ApiContext.getApiConfig().setOtherException(new CustomException());
}
}
上述配置完成后,我們重寫啟動項目,再次調用的時候,已經拋出了主鍵錯誤的異常信息。