Dubbo的正確開啟方式之基本介面定義及異常處理
阿新 • • 發佈:2019-02-13
為什麼要寫這篇文章呢?因為公司在使用Dubbo時並沒有對Dubbo的用法做深入的瞭解,而是屬於拿來就用,隨著自己的想法來使用。這樣很不好,就像天龍八部的鳩摩智練習錯誤的六脈神劍一樣,方式不對,就容易走火入魔。下面是我近來在空閒時間對Dubbo的一些學習,也糾正了之前的一些錯誤用法,在此做一下記錄:
介面定義及異常處理
原來錯誤的做法
先說說最初使用時的做法:
由於Provider和Consumer實際上就是一個服務端和一個客戶端的關係。在實際應用當中,和APP直接互動的tomcat伺服器就是客戶端。那麼在我們最初對Dubbo不瞭解的情況下,就照著APP介面的設計依樣畫葫蘆,定義了一個ResponseDTO
public class ResponseDTO<T> implements Serializable {
/**
*
*/
private static final long serialVersionUID = 4082846602141879024L;
private boolean status = true;
private String msg;
private Exception exception;
private T data;
}
然後在Dubbo中提供介面時,會使用try...catch
ResponseDTO
中去。這樣一來有幾點壞處:
- 增加了程式碼的重複度和複雜度
- 可能會造成某些
Exception
在客戶端無法被反序列化 - 無法通過Dubbo提供的攔截器來處理異常
那麼,其實我們應該怎麼做呢?
直接就像定義普通方法那樣定義介面就好了,無需關心異常情況。因為Dubbo已經預設提供了ExceptionFilter
來幫助我們處理異常,這個Filter具體是用來幹什麼的呢?下面貼出核心程式碼,非常容易理解:
Throwable exception = result.getException();
// 如果是checked異常,直接丟擲
if (!(exception instanceof RuntimeException)
&& (exception instanceof Exception)) {
return result;
}
// 在方法簽名上有宣告,直接丟擲
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日誌
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包裡,直接丟擲
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null
|| serviceFile.equals(exceptionFile)) {
return result;
}
// 是JDK自帶的異常,直接丟擲
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return result;
}
// 是Dubbo本身的異常,直接丟擲
if (exception instanceof RpcException) {
return result;
}
// 否則,包裝成RuntimeException拋給客戶端
return new RpcResult(new RuntimeException(
StringUtils.toString(exception)));
還記得剛才我們提出自己定義ResponseDTO
的三個弊端嗎?這裡我們再列一下:
- 增加了程式碼的重複度和複雜度
- 可能會造成某些
Exception
在客戶端無法被反序列化 - 無法通過Dubbo提供的攔截器來處理異常
那麼再看看Dubbo是如何通過ExceptionFilter
來解決的:
- 在Dubbo中提供介面時,我們無需每個方法都用
try...catch
包裹,然後組裝ResponseDTO
物件。而在客戶端,也可以直接像呼叫本地方法一樣呼叫Dubbo
的方法,無需再處理ResponseDTO
物件 - 仔細看上面的核心程式碼,在解決的一個最核心的問題就是擔心客戶端無法反序列化,所以也就有了最後一行,將無法反序列化的
Exception
包裝成RuntimeException
- 我們可以通過自己寫Filter來處理異常。最典型的,可能有的公司要求監控error日誌,那麼並不需要打error日誌的異常我們可以通過重寫Filter來實現。
所以對於介面定義和異常處理來說,正確的開啟方式就是:
- 像定義普通方法一樣定義介面,客戶端直接呼叫即可
- 利用好Filter來處理異常資訊