1. 程式人生 > >Dubbo的正確開啟方式之基本介面定義及異常處理

Dubbo的正確開啟方式之基本介面定義及異常處理

為什麼要寫這篇文章呢?因為公司在使用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來實現。

所以對於介面定義和異常處理來說,正確的開啟方式就是:

  1. 像定義普通方法一樣定義介面,客戶端直接呼叫即可
  2. 利用好Filter來處理異常資訊