歡迎光臨
每天分享高質量文章

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

來自:肥朝(公眾號ID:feichao_java)

前言

17年的時候,因為一時衝動沒把持住(當然最近也有粉絲叫我再衝動一把再更新一波),結合面試題寫了一個系列的Dubbo原始碼解析.目前公眾號大部分粉絲都是之前的粉絲,這裡不過多介紹.

根據我的面試經驗而言,能在簡歷上寫上原理原始碼等關鍵詞的,是非常具備核心競爭力的.上週和一個公眾號粉絲交流面試情況如下

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

“你對Dubbo原始碼這麼熟悉,那請問你使用的時候,有沒有遇到什麼坑”

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

論如何反殺

相信大家面試都遇到過類似問題,因為原始碼解析網上很多,很多人”考前突擊”一下,但是遇到喜歡問細節的面試官,終究難逃法眼,無處遁形.遇到這個問題,我們如何反殺一波?那麼我就從一次聊天記錄說起,畢竟只有關註肥朝公眾號,擁有真實場景的原始碼實戰(非常重要),遇到這類問題,才不至於出現猛虎落淚的情形

真實場景描述

那麼我們把業務相關去掉,抽取一個最簡模型.我們在公司,一般都會有自己的自定義異常,然後這個自定義異常一般放在common.jar給其他模組依賴,比如我這裡定義一個HelloException

 1public class HelloException extends RuntimeException {
 2
 3    public HelloException() {
 4    }
 5
 6    public HelloException(String message) {
 7        super(message);
 8    }
 9
10}

然後我們寫一個最簡單的Dubbo的demo,如下

interface

1public interface DemoService {
2
3    String sayHello(String name);
4
5}

provider

1public class DemoServiceImpl implements DemoService {
2
3    public String sayHello(String name) {
4        throw new HelloException("公眾號:肥朝");
5    }
6
7}

consumer

 1public class DemoAction {
 2
 3    private DemoService demoService;
 4
 5    public void setDemoService(DemoService demoService) {
 6        this.demoService = demoService;
 7    }
 8
 9    public void start() throws Exception {
10        try {
11            String hello = demoService.sayHello("公眾號:肥朝");
12        } catch (HelloException helloException) {
13            System.out.println("這裡捕獲helloException異常");
14        }
15    }
16
17}

按照聊天記錄的描述,此時consumer呼叫provider,provider丟擲HelloException.但是consumer捕獲到的,卻不是HelloException.

那麼我們執行看看

果然如該同事所言.為什麼會這樣呢?之前沒看過肥朝Dubbo原始碼解析系列的同學這種時候往往採用最低效的解決辦法,把異常棧往微信群一丟,各種求助.但是往往毫無收穫,然後感嘆社會為何如此冷漠!

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

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

1com.alibaba.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:108)

那麼我們一探究竟

 1    public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException {
 2        try {
 3            Result result = invoker.invoke(invocation);
 4            if (result.hasException() && GenericService.class != invoker.getInterface()) {
 5                try {
 6                    Throwable exception = result.getException();
 7
 8                    // 如果是checked異常,直接丟擲
 9                    if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) {
10                        return result;
11                    }
12                    // 在方法簽名上有宣告,直接丟擲
13                    try {
14                        Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
15                        Class>[] exceptionClassses = method.getExceptionTypes();
16                        for (Class> exceptionClass : exceptionClassses) {
17                            if (exception.getClass().equals(exceptionClass)) {
18                                return result;
19                            }
20                        }
21                    } catch (NoSuchMethodException e) {
22                        return result;
23                    }
24
25                    // 未在方法簽名上定義的異常,在伺服器端列印ERROR日誌
26                    logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
27                            + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
28                            + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
29
30                    // 異常類和介面類在同一jar包裡,直接丟擲
31                    String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
32                    String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
33                    if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){
34                        return result;
35                    }
36                    // 是JDK自帶的異常,直接丟擲
37                    String className = exception.getClass().getName();
38                    if (className.startsWith("java.") || className.startsWith("javax.")) {
39                        return result;
40                    }
41                    // 是Dubbo本身的異常,直接丟擲
42                    if (exception instanceof RpcException) {
43                        return result;
44                    }
45
46                    // 否則,包裝成RuntimeException拋給客戶端
47                    return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
48                } catch (Throwable e) {
49                    logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
50                            + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
51                            + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
52                    return result;
53                }
54            }
55            return result;
56        } catch (RuntimeException e) {
57            logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
58                    + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
59                    + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
60            throw e;
61        }
62    }

手機上閱讀原始碼或許並不友好,但是沒關係,上面都有完善的中文註釋,他想表達的意思如下:

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

    贊(0)

    分享創造快樂