反射與動(dòng)態(tài)代理的應(yīng)用(一):在RPC中的使用

在上一篇文章中啃憎,講解了反射 與動(dòng)態(tài)代理基本概念颂跨,沒(méi)有看過(guò)點(diǎn)擊此傳送門
接下來(lái)看看犬缨,動(dòng)態(tài)代理在RPC中是如何使用的喳魏。在講解過(guò)程中會(huì)去除掉一些無(wú)關(guān)的代碼,如果讀者相應(yīng)看全部的代碼怀薛,可以去開(kāi)源項(xiàng)目的倉(cāng)庫(kù)中下載相關(guān)源碼刺彩,進(jìn)一步的專研,以下也會(huì)給出鏈接枝恋。

實(shí)例

1.第一個(gè)實(shí)例取自黃勇的輕量級(jí)分布式 RPC 框架 创倔,由于實(shí)現(xiàn)中通信框架使用了Netty,所以在分析中會(huì)有部分Netty代碼的信息焚碌,不過(guò)不用擔(dān)心畦攘,即使不懂Netty,講解的過(guò)程中會(huì)盡量避免十电,并會(huì)突出反射與動(dòng)態(tài)代理在其中的作用知押。
在rpc-simple-client中HelloClient.Class有如下代碼

HelloService helloService = rpcProxy.create(HelloService.class);
String result = helloService.hello("World");
System.out.println(result);

這個(gè)代碼做的是什么事呢?通過(guò)一個(gè)代理生成helloService對(duì)象鹃骂,執(zhí)行hello方法台盯。
在我們印象中執(zhí)行方法,最終都會(huì)執(zhí)行的是接口中實(shí)現(xiàn)的方法畏线。那事實(shí)是這樣嗎静盅?看下面的分析。
在rpcProxy代碼如下:

 public <T> T create(final Class<?> interfaceClass, final String serviceVersion) {
        // 創(chuàng)建動(dòng)態(tài)代理對(duì)象
        return (T) Proxy.newProxyInstance(
                interfaceClass.getClassLoader(),
                new Class<?>[]{interfaceClass},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 創(chuàng)建 RPC 請(qǐng)求對(duì)象并設(shè)置請(qǐng)求屬性
                        RpcRequest request = new RpcRequest();
                        request.setRequestId(UUID.randomUUID().toString());
                        request.setInterfaceName(method.getDeclaringClass().getName());
                        request.setServiceVersion(serviceVersion);
                        request.setMethodName(method.getName());
                        request.setParameterTypes(method.getParameterTypes());
                        request.setParameters(args);
                        // 獲取 RPC 服務(wù)地址
                        if (serviceDiscovery != null) {
                            String serviceName = interfaceClass.getName();
                            if (StringUtil.isNotEmpty(serviceVersion)) {
                                serviceName += "-" + serviceVersion;
                            }
                            serviceAddress = serviceDiscovery.discover(serviceName);
                            LOGGER.debug("discover service: {} => {}", serviceName, serviceAddress);
                        }
                        if (StringUtil.isEmpty(serviceAddress)) {
                            throw new RuntimeException("server address is empty");
                        }
                        // 從 RPC 服務(wù)地址中解析主機(jī)名與端口號(hào)
                        String[] array = StringUtil.split(serviceAddress, ":");
                        String host = array[0];
                        int port = Integer.parseInt(array[1]);
                        // 創(chuàng)建 RPC 客戶端對(duì)象并發(fā)送 RPC 請(qǐng)求
                        RpcClient client = new RpcClient(host, port);
                        long time = System.currentTimeMillis();
                        RpcResponse response = client.send(request);
                        LOGGER.debug("time: {}ms", System.currentTimeMillis() - time);
                        if (response == null) {
                            throw new RuntimeException("response is null");
                        }
                        // 返回 RPC 響應(yīng)結(jié)果
                        if (response.hasException()) {
                            throw response.getException();
                        } else {
                            return response.getResult();
                        }
                    }
                }
        );
    }

從上面的代碼可以看出經(jīng)過(guò)了代理寝殴,執(zhí)行hello方法蒿叠,其實(shí)是發(fā)起一個(gè)請(qǐng)求。既然是一個(gè)請(qǐng)求杯矩,就是要涉及Client端與Server端栈虚,上面其實(shí)是一個(gè)Clent端代碼袖外。
那我們看看Server做了什么,去掉一個(gè)和本文所介紹不相關(guān)的代碼史隆,在RpcServerHandler中可以看核心代碼如下

  public void channelRead0(final ChannelHandlerContext ctx, RpcRequest request) throws Exception {
   Object result = handle(request);
   response.setResult(result);
   ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);  //返回,異步關(guān)閉連接
}
  其中hanlde中重要實(shí)現(xiàn)如下
       // 獲取反射調(diào)用所需的參數(shù)曼验,這些都是Client端傳輸給我們的泌射。
  Class<?> serviceClass = serviceBean.getClass();
  String methodName = request.getMethodName();
  Class<?>[] parameterTypes = request.getParameterTypes();
  Object[] parameters = request.getParameters();
   // 使用 CGLib 執(zhí)行反射調(diào)用
   FastClass serviceFastClass = FastClass.create(serviceClass);
   FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes);
   return serviceFastMethod.invoke(serviceBean, parameters);

2.第二個(gè)實(shí)例取自xxl-job分布式任務(wù)調(diào)度平臺(tái)
說(shuō)明:此開(kāi)源項(xiàng)目的粘姜,RPC通信是用Jetty來(lái)實(shí)現(xiàn)的。

在xxl-job-admin中XxlJobTrigger.Class的runExecutor有如下

  ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);  //根據(jù)地址拿到執(zhí)行器
  runResult = executorBiz.run(triggerParam);

做了很簡(jiǎn)單的是取出執(zhí)行器熔酷,觸發(fā)執(zhí)行孤紧。但是進(jìn)入getExecutorBiz方法你會(huì)發(fā)現(xiàn)如下

  executorBiz = (ExecutorBiz) new NetComClientProxy(ExecutorBiz.class, address, 
                           accessToken).getObject();
  executorBizRepository.put(address, executorBiz);
  return executorBiz;

是不是很熟悉,沒(méi)錯(cuò)拒秘,動(dòng)態(tài)代理号显,看是NetComClientProxy的實(shí)現(xiàn):
在結(jié)構(gòu)上是不是和第一個(gè)實(shí)例中的rpcProxy代碼,很相似呢躺酒。
new NetComClientProxy(ExecutorBiz.class, address, accessToken).getObject();做了什么呢

public Object getObject() throws Exception {
        return Proxy.newProxyInstance(Thread.currentThread()
                .getContextClassLoader(), new Class[] { iface },
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        
                        // request封裝
                        RpcRequest request = new RpcRequest();
                        request.setServerAddress(serverAddress);
                        request.setCreateMillisTime(System.currentTimeMillis());
                        request.setAccessToken(accessToken);
                        request.setClassName(method.getDeclaringClass().getName());
                        request.setMethodName(method.getName());
                        request.setParameterTypes(method.getParameterTypes());
                        request.setParameters(args);
                        
                        // send發(fā)送
                        RpcResponse response = client.send(request);
                        
                        // valid response
                        if (response == null) {
                            logger.error(">>>>>>>>>>> xxl-rpc netty response not found.");
                            throw new Exception(">>>>>>>>>>> xxl-rpc netty response not found.");
                        }
                        if (response.isError()) {
                            throw new RuntimeException(response.getError());
                        } else {
                            return response.getResult();
                        }
                       
                    }
                });
    }

依舊是封裝了一個(gè)RpcRequest 押蚤,發(fā)送請(qǐng)求。所以在 runResult = executorBiz.run(triggerParam)
其實(shí)是在發(fā)送一個(gè)請(qǐng)求羹应。上面是Client端代碼揽碘,照舊,接著看Server代碼园匹,你會(huì)發(fā)現(xiàn)還是似成相識(shí)雳刺。去掉與本文無(wú)關(guān)的代碼,得到如下:
在xxl-job-core中JettyServerHandler.Class

RpcResponse rpcResponse = NetComServerFactory.invokeService(rpcRequest, null);
點(diǎn)擊進(jìn)入:
public static RpcResponse invokeService(RpcRequest request, Object serviceBean) {
Class<?> serviceClass = serviceBean.getClass();  //類名
            String methodName = request.getMethodName();    //方法名run
            Class<?>[] parameterTypes = request.getParameterTypes();  //參數(shù)類型
            Object[] parameters = request.getParameters();   //具體參數(shù)

            FastClass serviceFastClass = FastClass.create(serviceClass);
            FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes);
            // 使用 CGLib 執(zhí)行反射調(diào)用
            Object result = serviceFastMethod.invoke(serviceBean, parameters);
            response.setResult(result);
        } catch (Throwable t) {
            t.printStackTrace();
            response.setError(t.getMessage());
        }
        return response;
}

根據(jù)反射生成具體的類裸违,來(lái)執(zhí)行相關(guān)的方法掖桦,達(dá)到想要的目的。
上面兩個(gè)實(shí)例的過(guò)程可以用下圖概括:


具體過(guò)程.png

由于本人水平有限供汛,有什么問(wèn)題可以評(píng)論滞详,喜歡的可以關(guān)注。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末紊馏,一起剝皮案震驚了整個(gè)濱河市料饥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌朱监,老刑警劉巖岸啡,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赫编,居然都是意外死亡巡蘸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門擂送,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)悦荒,“玉大人,你說(shuō)我怎么就攤上這事嘹吨“嵛叮” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)碰纬。 經(jīng)常有香客問(wèn)我萍聊,道長(zhǎng),這世上最難降的妖魔是什么悦析? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任寿桨,我火速辦了婚禮,結(jié)果婚禮上强戴,老公的妹妹穿的比我還像新娘亭螟。我一直安慰自己,他們只是感情好骑歹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布媒佣。 她就那樣靜靜地躺著,像睡著了一般陵刹。 火紅的嫁衣襯著肌膚如雪默伍。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天衰琐,我揣著相機(jī)與錄音也糊,去河邊找鬼。 笑死羡宙,一個(gè)胖子當(dāng)著我的面吹牛狸剃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播狗热,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼钞馁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了匿刮?” 一聲冷哼從身側(cè)響起僧凰,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熟丸,沒(méi)想到半個(gè)月后训措,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡光羞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年绩鸣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纱兑。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呀闻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出潜慎,到底是詐尸還是另有隱情捡多,我是刑警寧澤蓖康,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站局服,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏驳遵。R本人自食惡果不足惜淫奔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望堤结。 院中可真熱鬧唆迁,春花似錦、人聲如沸竞穷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瘾带。三九已至鼠哥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間看政,已是汗流浹背朴恳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留允蚣,地道東北人于颖。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像嚷兔,于是被迫代替她去往敵國(guó)和親森渐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理冒晰,服務(wù)發(fā)現(xiàn)同衣,斷路器,智...
    卡卡羅2017閱讀 134,656評(píng)論 18 139
  • 《分布式任務(wù)調(diào)度平臺(tái)XXL-JOB》 一壶运、簡(jiǎn)介 1.1 概述 XXL-JOB是一個(gè)輕量級(jí)分布式任務(wù)調(diào)度框架乳怎,其核心...
    許雪里閱讀 16,793評(píng)論 3 29
  • 今天分布式應(yīng)用、云計(jì)算前弯、微服務(wù)大行其道蚪缀,作為其技術(shù)基石之一的 RPC 你了解多少?一篇 RPC 的技術(shù)總結(jié)文章恕出,數(shù)...
    零一間閱讀 1,893評(píng)論 1 46
  • 人生沒(méi)有什么事是十全十美的询枚,總有一天它會(huì)以另一種方式出現(xiàn)在你面前,當(dāng)您還是一臉懵逼的時(shí)候浙巫,就像晴天霹靂一樣降落到你...
    放縱的笑是我僅剩旳驕傲閱讀 292評(píng)論 0 0
  • 大三了金蜀,第一學(xué)期課很少刷后,有時(shí)候一周一天半的課,爽是爽但是也無(wú)聊渊抄,因?yàn)榭偸窃谒奚峋透杏X(jué)要發(fā)霉了尝胆,很頹廢,就想打破這個(gè)...
    Matcha_tree閱讀 288評(píng)論 0 0