記錄Dubbo
對於自定義異常的處理方式.前端
web
層統一捕獲處理{"code":xxx,"msg":yyy}
其中code
對應爲錯誤碼
,msg
對應爲異常信息{"code":-1,"msg":"未知錯誤"}
,同時將異常堆棧信息輸出到日誌,便於定位問題先來張系統架構圖吧,這張圖來源自網絡,相信如今大部分中小企業的分佈式集羣架構都是相似這樣的設計:java
簡要說明下分層架構:nginx
堡壘機
作統一的代理轉發,客戶端(pc,移動端等)訪問由nginx
統一暴露的入口nginx
反向代理,負載均衡到web
服務器,由tomcat
組成的集羣,web
層僅僅是做爲接口請求的入口,沒有實際的業務邏輯web
層再用rpc
遠程調用註冊到zookeeper
的dubbo
服務集羣,dubbo
服務與數據層交互,處理業務邏輯先後端分離,使用json
格式作數據交互,格式能夠統一以下:web
{
"code": 200, //狀態碼:200成功,其餘爲失敗
"msg": "success", //消息,成功爲success,其餘爲失敗緣由
"data": object  //具體的數據內容,能夠爲任意格式
}
複製代碼
映射爲javabean
能夠統必定義爲:數據庫
/** * @program: easywits * @description: http請求 返回的最外層對象 * @author: zhangshaolin * @create: 2018-04-27 10:43 **/
@Data
@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class BaseResult<T> implements Serializable{
private static final long serialVersionUID = -6959952431964699958L;
/** * 狀態碼:200成功,其餘爲失敗 */
public Integer code;
/** * 成功爲success,其餘爲失敗緣由 */
public String msg;
/** * 具體的內容 */
public T data;
}
複製代碼
返回結果工具類封裝:json
/** * @program: easywits * @description: http返回結果工具類 * @author: zhangshaolin * @create: 2018-07-14 13:38 **/
public class ResultUtil {
/** * 訪問成功時調用 包含data * @param object * @return */
public static BaseResult success(Object object){
BaseResult result = new BaseResult();
result.setCode(200);
result.setMsg("success");
result.setData(object);
return result;
}
/** * 訪問成功時調用 不包含data * @return */
public static BaseResult success(){
return success(null);
}
/** * 返回異常狀況 不包含data * @param code * @param msg * @return */
public static BaseResult error(Integer code,String msg){
BaseResult result = new BaseResult();
result.setCode(code);
result.setMsg(msg);
return result;
}
/** * 返回異常狀況 包含data * @param resultEnum 結果枚舉類 統一管理 code msg * @param object * @return */
public static BaseResult error(ResultEnum resultEnum,Object object){
BaseResult result = error(resultEnum);
result.setData(object);
return result;
}
/** * 全局基類自定義異常 異常處理 * @param e * @return */
public static BaseResult error(BaseException e){
return error(e.getCode(),e.getMessage());
}
/** * 返回異常狀況 不包含data * @param resultEnum 結果枚舉類 統一管理 code msg * @return */
public static BaseResult error(ResultEnum resultEnum){
return error(resultEnum.getCode(),resultEnum.getMsg());
}
}
複製代碼
所以,模擬一次前端調用請求的過程能夠以下:後端
web
層接口tomcat
@RestController
@RequestMapping(value = "/user")
public class UserController {
@Autowired
UserService mUserService;
@Loggable(descp = "用戶我的資料", include = "")
@GetMapping(value = "/info")
public BaseResult userInfo() {
return mUserService.userInfo();
}
}
複製代碼
服務層接口服務器
@Override
public BaseResult userInfo() {
UserInfo userInfo = ThreadLocalUtil.getInstance().getUserInfo();
UserInfoVo userInfoVo = getUserInfoVo(userInfo.getUserId());
return ResultUtil.success(userInfoVo);
}
複製代碼
定義一個自定義異常,用於手動拋出異常信息,注意這裏基礎RuntimeException
爲未受檢異常
:網絡
簡單說明,
RuntimeException
及其子類爲未受檢異常,其餘異常爲受檢異常,未受檢異常是運行時拋出的異常,而受檢異常則在編譯時則強則報錯
public class BaseException extends RuntimeException{
private Integer code;
public BaseException() {
}
public BaseException(ResultEnum resultEnum) {
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
}
...省略set get方法
}
複製代碼
爲了方便對結果統一管理,定義一個結果枚舉類:
public enum ResultEnum {
UNKNOWN_ERROR(-1, "o(╥﹏╥)o~~系統出異常啦!,請聯繫管理員!!!"),
SUCCESS(200, "success");
private Integer code;
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
複製代碼
web
層統一捕獲異常定義BaseController
抽象類,統一捕獲由服務層拋出的異常,全部新增Controller
繼承該類便可。
public abstract class BaseController {
private final static Logger LOGGER = LoggerFactory.getLogger(BaseController.class);
/** * 統一異常處理 * * @param e */
@ExceptionHandler()
public Object exceptionHandler(Exception e) {
if (e instanceof BaseException) {
//全局基類自定義異常,返回{code,msg}
BaseException baseException = (BaseException) e;
return ResultUtil.error(baseException);
} else {
LOGGER.error("系統異常: {}", e);
return ResultUtil.error(ResultEnum.UNKNOWN_ERROR);
}
}
}
複製代碼
以上web
層接口UserController
繼承BaseController
,統一捕獲異常
服務層假設拋出自定義系統異常BaseException
,代碼以下:
@Override
public BaseResult userInfo() {
UserInfo userInfo = ThreadLocalUtil.getInstance().getUserInfo();
UserInfoVo userInfoVo = getUserInfoVo(userInfo.getUserId());
if (userInfoVo != null) {
//這裏假設拋個自定義異常,返回結果{code:10228 msg:"用戶存在!"}
throw new BaseException(ResultEnum.USER_EXIST);
}
return ResultUtil.success(userInfoVo);
}
複製代碼
然而調用結果後,上層捕獲到的異常卻不是BaseException
,而被認爲了未知錯誤拋出了.帶着疑問看看Dubbo
對於異常的處理
Dubbo
對於異常有統一的攔截處理,如下是Dubbo
異常攔截器主要代碼:
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
try {
// 服務調用
Result result = invoker.invoke(invocation);
// 有異常,而且非泛化調用
if (result.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = result.getException();
// directly throw if it's checked exception
// 若是是checked異常,直接拋出
if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) {
return result;
}
// directly throw if the exception appears in the signature
// 在方法簽名上有聲明,直接拋出
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClassses = method.getExceptionTypes();
for (Class<?> exceptionClass : exceptionClassses) {
if (exception.getClass().equals(exceptionClass)) {
return result;
}
}
} catch (NoSuchMethodException e) {
return result;
}
// 未在方法簽名上定義的異常,在服務器端打印 ERROR 日誌
// for the exception not found in method's signature, print ERROR message in server's log.
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
// 異常類和接口類在同一 jar 包裏,直接拋出
// directly throw if exception class and interface class are in the same jar file.
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
return result;
}
// 是JDK自帶的異常,直接拋出
// directly throw if it's JDK exception
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return result;
}
// 是Dubbo自己的異常,直接拋出
// directly throw if it's dubbo exception
if (exception instanceof RpcException) {
return result;
}
// 不然,包裝成RuntimeException拋給客戶端
// otherwise, wrap with RuntimeException and throw back to the client
return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
} catch (Throwable e) {
logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
return result;
}
}
// 返回
return result;
} catch (RuntimeException e) {
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
throw e;
}
}
複製代碼
簡要說明:
jar
包裏,則直接拋出Dubbo
自己的異常(RpcException),則直接拋出RuntimeException
拋給客戶端到如今問題很明顯了,咱們自定義的BaseException
爲未受檢異常
,何況不符合Dubbo
異常攔截器中直接拋出的要求,Dubbo
將其包裝成了RuntimeException
,因此在上層BaseController
中統一捕獲爲系統未知錯誤了.
BaseException
和接口類在同一 jar
包裏,可是這種方式要在每一個jar
中放置一個異常類,很差統一維護管理BaseException
,這種方式相對簡單一些,比較好統一維護,只是每一個接口都要顯式聲明一下異常罷了,這裏我選擇這種方式解決爲何必定要拋出自定義異常來中斷程序運行,用return ResultUtil.error(ResultEnum resultEnum)
強制返回{code:xxx msg:xxx}
結果,不是同樣能夠強制中斷程序運行?
玩過Spring
的確定都知道,Spring
喲聲明式事物的概念,即在接口中添加事物註解,當發生異常時,所有接口執行事物回滾..看下方的僞代碼:
@Transactional(rollbackFor = Exception.class)
public BaseResult handleData(){
//1. 操做數據庫,新增數據表A一條數據,返回新增數據主鍵id
//2. 操做數據庫,新增數據庫B一條數據,以數據表A主鍵id爲外鍵關聯
//3. 執行成功 返回結果
}
複製代碼
在實際問題場景中去閱讀源碼是最合適的,帶着問題有目的的看指定源碼會讓人有豁然開朗的感受.
更多原創文章會第一時間推送公衆號【張少林同窗】,歡迎關注!