[肥朝]面試官問我,使用Dubbo有沒有遇到一些坑?我笑了。

前言

17年的時候,由於一時衝動沒把持住(固然最近也有粉絲叫我再衝動一把再更新一波),結合面試題寫了一個系列的Dubbo源碼解析.目前公衆號大部分粉絲都是以前的粉絲,這裏不過多介紹.java

根據個人面試經驗而言,能在簡歷上寫上原理源碼等關鍵詞的,是很是具有核心競爭力的.上週和一個公衆號粉絲交流面試狀況以下面試

面試的時候,把源碼一波分析,令面試官虎軀一震!在一陣前戲事後,覺得接下來無非就是身體的一頓抽搐一切變得索然無味,不料面試官來了句令劇情發生了反轉api

"你對Dubbo源碼這麼熟悉,那請問你使用的時候,有沒有遇到什麼坑"服務器

我擦,毫無準備的他,菊花頓時一緊!此時就面臨唬住了50K,唬不住就只能5K的局面,慌了!微信

論如何反殺

相信你們面試都遇到過相似問題,由於源碼解析網上不少,不少人"考前突擊"一下,可是遇到喜歡問細節的面試官,終究難逃法眼,無處遁形.遇到這個問題,咱們如何反殺一波?那麼我就從一次聊天記錄提及,畢竟只有關注肥朝公衆號,擁有 真實場景的源碼實戰(很是重要),遇到這類問題,纔不至於出現猛虎落淚的情形ide

真實場景描述

那麼咱們把業務相關去掉,抽取一個最簡模型.咱們在公司,通常都會有本身的自定義異常,而後這個自定義異常通常放在common.jar給其餘模塊依賴,好比我這裏定義一個HelloExceptionthis

public class HelloException extends RuntimeException {

    public HelloException() {
    }

    public HelloException(String message) {
        super(message);
    }

}
複製代碼

而後咱們寫一個最簡單的Dubbo的demo,以下spa

interface設計

public interface DemoService {

    String sayHello(String name);

}
複製代碼

provider3d

public class DemoServiceImpl implements DemoService {

    public String sayHello(String name) {
        throw new HelloException("公衆號:肥朝");
    }

}
複製代碼

consumer

public class DemoAction {

    private DemoService demoService;

    public void setDemoService(DemoService demoService) {
        this.demoService = demoService;
    }

    public void start() throws Exception {
        try {
            String hello = demoService.sayHello("公衆號:肥朝");
        } catch (HelloException helloException) {
            System.out.println("這裏捕獲helloException異常");
        }
    }

}
複製代碼

按照聊天記錄的描述,此時consumer調用provider,provider拋出HelloException.可是consumer捕獲到的,卻不是HelloException.

那麼咱們運行看看

果真如該同事所言.爲何會這樣呢?以前沒看過肥朝Dubbo源碼解析系列的同窗這種時候每每採用最低效的解決辦法,把異常棧往微信羣一丟,各類求助.可是每每毫無收穫,而後感嘆社會爲什麼如此冷漠!

可是相信公衆號的老粉絲們早已掌握閱讀源碼的技能,和肥朝同樣坐懷不亂,九淺一深直入源碼.出現異常咱們首先看一下異常棧

除非擼多了看不清(建議戒擼),不然這行異常和肥朝同樣,就像漆黑中的螢火蟲同樣,那麼鮮明,那麼出衆

com.alibaba.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:108)
複製代碼

那麼咱們一探究竟

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();

                    // 若是是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)));
                } 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;
        }
    }
複製代碼

手機上閱讀源碼或許並不友好,可是不要緊,上面都有完善的中文註釋,他想表達的意思以下:

1.若是是checked異常,直接拋出.很明顯,咱們的HelloExceptionRuntimeException,不符合

2.在方法簽名上有聲明,直接拋出.很明顯,咱們接口並未聲明該異常,不符合

3.異常類和接口類在同一jar包裏,直接拋出.很明顯,咱們的異常類是在common.jar的,接口是在api.jar的,不符合

4.是JDK自帶的異常,直接拋出.很明顯,這個HelloException是咱們自定義的,不符合

5.是Dubbo自己的異常(RpcException),直接拋出.很明顯,這個HelloException是咱們自定義的,和RpcException幾乎沒有半毛錢關係.

6.不然,包裝成RuntimeException拋給客戶端.由於以上5點均不知足,因此該異常會被包裝成RuntimeException異常拋出(重要)

這也就是爲何咱們catchHelloException是catch不到的,由於他包裝成RuntimeException

Dubbo爲何這麼設計

也許你看到這裏會以爲這個判斷好坑.Dubbo爲何要這麼設計?咱們看源碼,最重要的是知道做者爲何這麼設計,只有知道爲何這麼設計纔是通過了深度的思考,不然看時高潮,看後就忘.講清楚爲何這麼設計,也是你們關注肥朝公衆號的一個重要緣由.

其實Dubbo的這個考慮,是基於序列化來考慮的.你想一想,若是provider拋出一個僅在provider自定義的一個異常,那麼該異常到達consumer,明顯是沒法序列化的.因此你注意看Dubbo的判斷.咱們來看下他的判斷

1.checked異常和RuntimeException是不一樣類型,強行包裝可能會出現類型轉換錯誤,所以不包,直接拋出

2.方法簽名上有聲明.方法簽名上有聲明,若是這個異常是provider.jar中定義的,由於consumer是依賴api.jar的,而不是依賴provider.jar.那麼編譯都編譯不過,若是能編譯得過,說明consumer是能依賴到這個異常的,所以序列化不會有問題,直接拋出

3.異常類和接口類在同一jar包裏.provider和consumer都依賴api,若是異常在這個api,那序列化也不會有問題,直接拋出

4.是JDK自帶的異常,直接拋出.provider和consumer都依賴jdk,序列化也不會有問題,直接拋出

5.是Dubbo自己的異常(RpcException),直接拋出.provider和consumer都依賴Dubbo,序列化也不會有問題,直接拋出

6.不然,包裝成RuntimeException拋給客戶端.此時,就有可能出現我說的那種,這個異常是provider.jar自定義的,那麼provider拋出的時候進行序列化,由於consumer沒有依賴provider.jar,因此異常到達consumer時,根本沒法反序列化.可是包裝成了RuntimeException異常則不一樣,此時異常就是JDK中的類了,到哪都能序列化.

如何解決

既然都知道了原理了,那麼很好解決,我隨便列舉一下,好比從規範上要求業務方接口聲明HelloException

寫在最後

固然肥朝面試的時候,也曾經被問過相似問題,你用XXX有沒有遇到過什麼坑.在一波操做猛如虎的分析下,面試官說

"你真帥".

肥朝會心一笑

結果他卻說

"你笑起來更帥"!


肥朝 是一個專一於 原理、源碼、開發技巧的技術公衆號,號內原創專題式源碼解析、真實場景源碼原理實戰(重點)。掃描下面二維碼關注肥朝,讓本該造火箭的你,再也不擰螺絲!

相關文章
相關標籤/搜索