關(guān)注公眾號“:Java架構(gòu)師聯(lián)盟,每日更新技術(shù)好文
代理模式與RPC客戶端實現(xiàn)類
本節(jié)首先介紹客戶端RPC遠程調(diào)用實現(xiàn)類的職責(zé),然后從基礎(chǔ)原理講起,依次介紹代理模式的原理、使用靜態(tài)代理模式實現(xiàn)RPC客戶端類、使用動態(tài)代理模式實現(xiàn)RPC客戶端類,一步一步地接近Feign RPC的核心原理知識。
客戶端RPC遠程調(diào)用實現(xiàn)類的職責(zé)
客戶端RPC實現(xiàn)類位于遠程調(diào)用Java接口和Provider微服務(wù)實例之間,承擔(dān)了以下職責(zé):
(1)拼裝REST請求:根據(jù)Java接口的參數(shù),拼裝目標(biāo)REST接口的URL。
(2)發(fā)送請求和獲取結(jié)果:通過Java HTTP組件(如HttpClient)調(diào)用Provider微服務(wù)實例的REST接口,并且獲取REST響應(yīng)。
(3)結(jié)果解碼:解析REST接口的響應(yīng)結(jié)果,封裝成目標(biāo)POJO對象(Java接口的返回類型)并且返回。
RPC遠程調(diào)用客戶端實現(xiàn)類的職責(zé)如圖3-1所示。
圖3-1 RPC遠程調(diào)用客戶端實現(xiàn)類的職責(zé)
使用Feign進行RPC遠程調(diào)用時,對于每一個Java遠程調(diào)用接口,F(xiàn)eign都會生成一個RPC遠程調(diào)用客戶端實現(xiàn)類,只是對于開發(fā)者來說這個實現(xiàn)類是透明的,感覺不到這個實現(xiàn)類的存在。
Feign為DemoClient接口生成的RPC客戶端實現(xiàn)類大致如圖3-2所示。
圖3-2 Feign為DemoClient接口生成的RPC客戶端實現(xiàn)類參考圖
由于看不到Feign的RPC客戶端實現(xiàn)類的任何源碼,初學(xué)者會感覺到很神奇,感覺這就是一個黑盒子。下面從原始的、簡單的RPC遠程調(diào)用客戶端實現(xiàn)類開始為大家逐步地揭開Feign的RPC客戶端實現(xiàn)類的神秘面紗。
在一點點揭開RPC遠程調(diào)用客戶端實現(xiàn)類的面紗之前,先模擬一個Feign遠程調(diào)用Java接口,對應(yīng)demo-provider服務(wù)的兩個REST接口。
模擬的遠程調(diào)用Java接口為MockDemoClient,它的代碼如下:
package com.crazymaker.demo.proxy.FeignMock;
...
@RestController(value = TestConstants.DEMO_CLIENT_PATH)
public interface MockDemoClient
{ /**
*遠程調(diào)用接口的方法,完成REST接口api/demo/hello/v1的遠程調(diào)用
*REST接口功能:返回hello world
@return JSON響應(yīng)實例
/
@GetMapping(name = "api/demo/hello/v1")
RestOut<JSONObject> hello();
/
*遠程調(diào)用接口的方法,完成REST接口api/demo/echo/{0}/v1的遠程調(diào)用
*REST接口功能:回顯輸入的信息
*@return echo回顯消息JSON響應(yīng)實例
*/
@GetMapping(name = "api/demo/echo/{0}/v1")
RestOut<JSONObject> echo(String word);
}</pre>
接下來層層遞進,為大家演示以下3種RPC遠程調(diào)用客戶端:
(1)簡單的RPC客戶端實現(xiàn)類。
(2)靜態(tài)代理模式的RPC客戶端實現(xiàn)類。
(3)動態(tài)代理模式的RPC客戶端實現(xiàn)類。
最后的動態(tài)代理模式的RPC客戶端實現(xiàn)類在實現(xiàn)原理上已經(jīng)非常接近Feign的RPC客戶端實現(xiàn)類。
簡單的RPC客戶端實現(xiàn)類
簡單的RPC客戶端實現(xiàn)類的主要工作如下:
(1)組裝REST接口URL。
(2)通過HttpClient組件調(diào)用REST接口并獲得響應(yīng)結(jié)果。
(3)解析REST接口的響應(yīng)結(jié)果,封裝成JSON對象,并且返回給調(diào)用者。
簡單的RPC客戶端實現(xiàn)類的參考代碼如下:
package com.crazymaker.demo.proxy.basic;
//省略import
@AllArgsConstructor
@Slf4j
class RealRpcDemoClientImpl implements MockDemoClient
{
final String contextPath = TestConstants.DEMO_CLIENT_PATH;
//完成對REST接口api/demo/hello/v1的調(diào)用
public RestOut<JSONObject> hello()
{
/**
*遠程調(diào)用接口的方法,完成demo-provider的REST API遠程調(diào)用
REST API功能:返回hello world
/
String uri = "api/demo/hello/v1";
/
組裝REST接口URL
/
String restUrl = contextPath + uri;
log.info("restUrl={}", restUrl);
/
通過HttpClient組件調(diào)用REST接口
/
String responseData = null;
try
{
responseData = HttpRequestUtil.simpleGet(restUrl);
} catch (IOException e)
{
e.printStackTrace();
}
/
解析REST接口的響應(yīng)結(jié)果,解析成JSON對象并且返回給調(diào)用者
/
RestOut<JSONObject> result = JsonUtil.jsonToPojo(responseData,
new TypeReference<RestOut<JSONObject>>() {});
return result;
}
//完成對REST接口api/demo/echo/{0}/v1的調(diào)用
public RestOut<JSONObject> echo(String word)
{
/
*遠程調(diào)用接口的方法,完成demo-provider的REST API遠程調(diào)用
REST API功能:回顯輸入的信息
/
String uri = "api/demo/echo/{0}/v1";
/
組裝REST接口URL
/
String restUrl = contextPath + MessageFormat.format(uri, word);
log.info("restUrl={}", restUrl);
/
通過HttpClient組件調(diào)用REST接口
/
String responseData = null;
try
{
responseData = HttpRequestUtil.simpleGet(restUrl);
} catch (IOException e)
{
e.printStackTrace();
}
/
解析
接
的響應(yīng)結(jié)果
解析成
對象
并且返回給調(diào)用者 *解析REST接口的響應(yīng)結(jié)果,解析成JSON對象,并且返回給調(diào)用者
*/
RestOut<JSONObject> result = JsonUtil.jsonToPojo(responseData,
new TypeReference<RestOut<JSONObject>>() { });
return result;
}
}</pre>
以上簡單的RPC實現(xiàn)類RealRpcDemoClientImpl的測試用例如下:
package com.crazymaker.demo.proxy.basic;
...
/**
測試用例
/
@Slf4j
public class ProxyTester
{
/
不用代理,進行簡單的遠程調(diào)用
/
@Test
public void simpleRPCTest()
{
/
簡單的RPC調(diào)用類
/
MockDemoClient realObject = new RealRpcDemoClientImpl();
/
調(diào)用demo-provider的REST接口api/demo/hello/v1
/
RestOut<JSONObject> result1 = realObject.hello();
log.info("result1={}", result1.toString());
/
*調(diào)用demo-provider的REST接口api/demo/echo/{0}/v1
*/
RestOut<JSONObject> result2 = realObject.echo("回顯內(nèi)容");
log.info("result2={}", result2.toString());
}
}</pre>
運行測試用例之前,需要提前啟動demo-provider微服務(wù)實例,然后將主機名稱crazydemo.com通過hosts文件綁定到demo-provider實例所在機器的IP地址(這里為127.0.0.1),并且需要確保兩個REST接口/api/demo/hello/v1、/api/demo/echo/{word}/v1可以正常訪問。
運行測試用例,部分輸出結(jié)果如下:
[main] INFO c.c.d.p.b.RealRpcDemoClientImpl - restUrl=http://crazydemo.com:7700/demo-provider/ api/demo/hello/v1
[main] INFO c.c.d.proxy.basic.ProxyTester - result1=RestOut{datas={"hello":"world"}, respCode=0, respMsg='操作成功}
[main] INFO c.c.d.p.b.RealRpcDemoClientImpl - restUrl=http://crazydemo.com:7700/demo-provider/ api/demo/echo/回顯內(nèi)容/v1
[main] INFO c.c.d.proxy.basic.ProxyTester - result2=RestOut{datas={"echo":"回顯內(nèi)容"}, respCode=0, respMsg='操作成功}</pre>
以上的RPC客戶端實現(xiàn)類很簡單,但是實際開發(fā)中不可能為每一個遠程調(diào)用Java接口都編寫一個RPC客戶端實現(xiàn)類。如何自動生成RPC客戶端實現(xiàn)類呢?這就需要用到代理模式。接下來為大家介紹簡單一點的代理模式實現(xiàn)類——靜態(tài)代理模式的RPC客戶端實現(xiàn)類。
從基礎(chǔ)原理講起:代理模式與RPC客戶端實現(xiàn)類
首先來看一下代理模式的基本概念。代理模式的定義:為委托對象提供一種代理,以控制對委托對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個目標(biāo)對象,而代理對象可以作為目標(biāo)對象的委托,在客戶端和目標(biāo)對象之間起到中介的作用。
代理模式包含3個角色:抽象角色、委托角色和代理角色,如圖3-3所示。
圖3-3 代理模式角色之間的關(guān)系圖
(1)抽象角色:通過接口或抽象類的方式聲明委托角色所提供的業(yè)務(wù)方法。
(2)代理角色:實現(xiàn)抽象角色的接口,通過調(diào)用委托角色的業(yè)務(wù)邏輯方法來實現(xiàn)抽象方法,并且可以附加自己的操作。
(3)委托角色:實現(xiàn)抽象角色,定義真實角色所要實現(xiàn)的業(yè)務(wù)邏輯,供代理角色調(diào)用。
代理模式分為靜態(tài)代理和動態(tài)代理。
(1)靜態(tài)代理:在代碼編寫階段由工程師提供代理類的源碼,再編譯成代理類。所謂靜態(tài),就是在程序運行前就已經(jīng)存在代理類的字節(jié)碼文件,代理類和被委托類的關(guān)系在運行前就確定了。
(2)動態(tài)代理:在代碼編寫階段不用關(guān)心具體的代理實現(xiàn)類,而是在運行階段直接獲取具體的代理對象,代理實現(xiàn)類由JDK負責(zé)生成。
靜態(tài)代理模式的實現(xiàn)主要涉及3個組件:(1)抽象接口類(Abstract Subject):該類的主要職責(zé)是聲明目標(biāo)類與代理類的共同接口方法。該類既可以是一個抽象類,又可以是一個接口。
(2)真實目標(biāo)類(Real Subject):該類也稱為被委托類或被代理類,該類定義了代理所表示的真實對象,由其執(zhí)行具體業(yè)務(wù)邏輯方法,而客戶端通過代理類間接地調(diào)用真實目標(biāo)類中定義的方法。
(3)代理類(Proxy Subject):該類也稱為委托類或代理類,該類持有一個對真實目標(biāo)類的引用,在其抽象接口方法的實現(xiàn)中需要調(diào)用真實目標(biāo)類中相應(yīng)的接口實現(xiàn)方法,以此起到代理的作用。
使用靜態(tài)代理模式實現(xiàn)RPC遠程接口調(diào)用大致涉及以下3個類:
(1)一個遠程接口,比如前面介紹的模擬遠程調(diào)用Java接口MockDemoClient。
(2)一個真實被委托類,比如前面介紹的RealRpcDemoClientImpl,負責(zé)完成真正的RPC調(diào)用。
(3)一個代理類,比如本小節(jié)介紹的DemoClientStaticProxy,通過調(diào)用真實目標(biāo)類(委托類)負責(zé)完成RPC調(diào)用。
通過靜態(tài)代理模式實現(xiàn)MockDemoClient接口的RPC調(diào)用實現(xiàn)類,類之間的關(guān)系如圖3-4所示。
圖3-4 靜態(tài)代理模式的RPC調(diào)用UML類圖
靜態(tài)代理模式的RPC實現(xiàn)類DemoClientStaticProxy的代碼如下:
package com.crazymaker.demo.proxy.basic;
//省略import
@AllArgsConstructor
@Slf4j
class DemoClientStaticProxy implements DemoClient
{
/**
*被代理的真正實例
*/
private MockDemoClient realClient; @Override
public RestOut<JSONObject> hello()
{
log.info("hello方法被調(diào)用" );
return realClient.hello();
}
@Override
public RestOut<JSONObject> echo(String word)
{
log.info("echo方法被調(diào)用" );
return realClient.echo(word);
}
}</pre>
在靜態(tài)代理類DemoClientStaticProxy的hello()和echo()兩個方法中,調(diào)用真實委托類實例realClient的兩個對應(yīng)的委托方法,完成對遠程REST接口的請求。
以上靜態(tài)代理類DemoClientStaticProxy的使用代碼(測試用例)大致如下:
package com.crazymaker.demo.proxy.basic;
//省略import
/**
靜態(tài)代理和動態(tài)代理,測試用例
/
@Slf4j
public class ProxyTester
{
/
靜態(tài)代理測試
/
@Test
public void staticProxyTest()
{
/
被代理的真實RPC調(diào)用類
/
MockDemoClient realObject = new RealRpcDemoClientImpl();
/
*靜態(tài)的代理類
*/
DemoClient proxy = new DemoClientStaticProxy(realObject);
RestOut<JSONObject> result1 = proxy.hello();
log.info("result1={}", result1.toString());
RestOut<JSONObject> result2 = proxy.echo("回顯內(nèi)容");
log.info("result2={}", result2.toString());
}
}</pre>
運行測試用例前,需要提前啟動demo-provider微服務(wù)實例,并且需要將主機名稱crazydemo.com通過hosts文件綁定到demo-provider實例所在機器的IP地址(這里為127.0.0.1),并且需要確保兩個REST接口/api/demo/hello/v1、/api/demo/echo/{word}/v1可以正常訪問。
一切準(zhǔn)備妥當(dāng),運行測試用例,輸出如下結(jié)果:
[main] INFO c.c.d.p.b.DemoClientStaticProxy - hello方法被調(diào)用
[main] INFO c.c.d.p.b.RealRpcDemoClientImpl - restUrl= http://crazydemo.com:7700/demo-provider/ api/demo/hello/v1
[main] INFO c.c.d.proxy.basic.ProxyTester - result1=RestOut{datas={"hello":"world"}, respCode=0, respMsg='操作成功}
[main] INFO c.c.d.p.b.DemoClientStaticProxy - echo方法被調(diào)用
[main] INFO c.c.d.p.b.RealRpcDemoClientImpl - restUrl=http://crazydemo.com:7700/demo-provider/ api/demo/echo/回顯內(nèi)容/v1
[main] INFO c.c.d.proxy.basic.ProxyTester - result2=RestOut{datas={"echo":"回顯內(nèi)容"}, respCode=0, respMsg='操作成功}</pre>
靜態(tài)代理的RPC實現(xiàn)類看上去是一堆冗余代碼,發(fā)揮不了什么作用。為什么在這里一定要先介紹靜態(tài)代理模式的RPC實現(xiàn)類呢?原因有以下兩點:
(1)上面的RPC實現(xiàn)類是出于演示目的而做了簡化,對委托類并沒有做任何擴展。而實際的遠程調(diào)用代理類會對委托類進行很多擴展,比如遠程調(diào)用時的負載均衡、熔斷、重試等。
(2)上面的RPC實現(xiàn)類是動態(tài)代理實現(xiàn)類的學(xué)習(xí)鋪墊。Feign的RPC客戶端實現(xiàn)類是一個JDK動態(tài)代理類,是在運行過程中動態(tài)生成的。大家知道,動態(tài)代理的知識對于很多讀者來說不是太好理解,所以先介紹一下代理模式和靜態(tài)代理的基礎(chǔ)知識,作為下一步的學(xué)習(xí)鋪墊。
使用動態(tài)代理模式實現(xiàn)RPC客戶端類
為什么需要動態(tài)代理呢?需要從靜態(tài)代理的缺陷開始介紹。靜態(tài)代理實現(xiàn)類在編譯期就已經(jīng)寫好了,代碼清晰可讀,缺點也很明顯:
(1)手工編寫代理實現(xiàn)類會占用時間,如果需要實現(xiàn)代理的類很多,那么代理類一個一個地手工編碼根本寫不過來。
(2)如果更改了抽象接口,那么還得去維護這些代理類,維護上容易出紕漏。
動態(tài)代理與靜態(tài)代理相反,不需要手工實現(xiàn)代理類,而是由JDK通過反射技術(shù)在執(zhí)行階段動態(tài)生成代理類,所以也叫動態(tài)代理。使用的時候可以直接獲取動態(tài)代理的實例,獲取動態(tài)代理實例大致需要如下3步:
(1)需要明確代理類和被委托類共同的抽象接口,JDK生成的動態(tài)代理類會實現(xiàn)該接口。
(2)構(gòu)造一個調(diào)用處理器對象,該調(diào)用處理器要實現(xiàn)InvocationHandler接口,實現(xiàn)其唯一的抽象方法invoke(...)。而InvocationHandler接口由JDK定義,位于java.lang.reflect包中。
(3)通過java.lang.reflect.Proxy類的newProxyInstance(...)方法在運行階段獲取JDK生成的動態(tài)代理類的實例。注意,這一步獲取的是對象而不是類。該方法需要三個參數(shù),其中的第一個參數(shù)為類裝載器,第二個參數(shù)為抽象接口的class對象,第三個參數(shù)為調(diào)用處理器對象。
舉一個例子,創(chuàng)建抽象接口MockDemoClient的一個動態(tài)代理實例,大致的代碼如下:
//參數(shù)1:類裝載器
ClassLoader classLoader = ProxyTester.class.getClassLoader();
//參數(shù)2:代理類和被委托類共同的抽象接口
Class[] clazz = new Class[]{MockDemoClient.class};
//參數(shù)3:動態(tài)代理的調(diào)用處理器
InvocationHandler invocationHandler = new DemoClientInocationHandler (realObject);
/**
*使用以上3個參數(shù)創(chuàng)建JDK動態(tài)代理類
*/
MockDemoClient proxy = (MockDemoClient)Proxy.newProxyInstance(classLoader, clazz, invocationHandler);</pre>
創(chuàng)建動態(tài)代理實例的核心是創(chuàng)建一個JDK調(diào)用處理器InvocationHandler的實現(xiàn)類。該實現(xiàn)類需要實現(xiàn)其唯一的抽象方法invoke(...),并且在該方法中調(diào)用被委托類的方法。一般情況下,調(diào)用處理器需要能夠訪問到被委托類,一般的做法是將被委托類實例作為其內(nèi)部的成員。
例子中所獲取的動態(tài)代理實例涉及3個類,具體如下:
(1)一個遠程接口,使用前面介紹的模擬遠程調(diào)用Java接口MockDemoClient。
(2)一個真實目標(biāo)類,使用前面介紹的RealRpcDemoClientImpl類,該類負責(zé)完成真正的RPC調(diào)用,作為動態(tài)代理的被委托類。
(3)一個InvocationHandler的實現(xiàn)類,本小節(jié)將實現(xiàn) DemoClientInocationHandler調(diào)用處理器類,該類通過調(diào)用內(nèi)部成員被委托類的對應(yīng)方法完成RPC調(diào)用。模擬遠程接口MockDemoClient的RPC動態(tài)代理模式實現(xiàn),類之間的關(guān)系如圖3-5所示。
圖3-5 動態(tài)代理模式實現(xiàn)RPC遠程調(diào)用UML類圖
通過動態(tài)代理模式實現(xiàn)模擬遠程接口MockDemoClient的RPC調(diào)用,關(guān)鍵的類為調(diào)用處理器,調(diào)用處理器 DemoClientInocationHandler的代碼如下:
package com.crazymaker.demo.proxy.basic;
//省略import
/**
動態(tài)代理的調(diào)用處理器
/
@Slf4j
public class DemoClientInocationHandler implements InvocationHandler
{
/
被代理的被委托類實例
/
private MockDemoClient realClient;
public DemoClientInocationHandler(MockDemoClient realClient)
{
this.realClient = realClient;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
String name = method.getName();
log.info("{} 方法被調(diào)用", method.getName());
/
直接調(diào)用被委托類的方法:調(diào)用其hello方法
/
if (name.equals("hello"))
{
return realClient.hello();
}
/
通過Java反射調(diào)用被委托類的方法:調(diào)用其echo方法
/
if (name.equals("echo"))
{
return method.invoke(realClient, args);
}
/
*通過Java反射調(diào)用被委托類的方法
*/
Object result = method.invoke(realClient, args);
return result;
}
}</pre>
調(diào)用處理器 DemoClientInocationHandler既實現(xiàn)了InvocationHandler接口,又擁有一個內(nèi)部被委托類成員,負責(zé)完成實際的RPC請求。調(diào)用處理器有點兒像靜態(tài)代理模式中的代理角色,但是在這里卻不是,僅僅是JDK所生成的代理類的內(nèi)部成員。
以上調(diào)用處理器 DemoClientInocationHandler的代碼(測試用例)如下:
package com.crazymaker.demo.proxy.basic;
//省略import
@Slf4j
public class StaticProxyTester {
/**
*動態(tài)代理測試
*/
@Test
public void dynamicProxyTest() {
DemoClient client = new DemoClientImpl();
//參數(shù)1:類裝載器
ClassLoader classLoader = StaticProxyTester.class.getClassLoader();
//參數(shù)2:被代理的實例類型
Class[] clazz = new Class[]{DemoClient.class};
//參數(shù)3:調(diào)用處理器
InvocationHandler invocationHandler =
new DemoClientInocationHandler(client);
//獲取動態(tài)代理實例
DemoClient proxy = (DemoClient)
Proxy.newProxyInstance(classLoader, clazz, invocationHandler);
//執(zhí)行RPC遠程調(diào)用方法
Result<JSONObject> result1 = proxy.hello();
log.info("result1={}", result1.toString());
Result<JSONObject> result2 = proxy.echo("回顯內(nèi)容");
log.info("result2={}", result2.toString());
}
}</pre>
運行測試用例前需要提前啟動demo-provider微服務(wù)實例,并且需要確保其兩個REST接口/api/demo/hello/v1、/api/demo/echo/{word}/v1可以正常訪問。
一切準(zhǔn)備妥當(dāng),運行測試用例,輸出的結(jié)果如下:
18:36:32.499 [main] INFO c.c.d.p.b.DemoClientInocationHandler - hello方法被調(diào)用
18:36:32.621 [main] INFO c.c.d.p.b.StaticProxyTester - result1=Result{data={"hello":"world"}, status=200, msg='操作成功, reques
18:36:32.622 [main] INFO c.c.d.p.b.DemoClientInocationHandler - echo方法被調(diào)用
18:36:32.622 [main] INFO c.c.d.p.b.StaticProxyTester - result2=Result{data={"echo":"回顯內(nèi)容"}, status=200, msg='操作成功, reques</pre>
JDK動態(tài)代理機制的原理
動態(tài)代理的實質(zhì)是通過java.lang.reflect.Proxy的newProxyInstance(...)方法生成一個動態(tài)代理類的實例,該方法比較重要,下面對該方法進行詳細介紹,其定義如下:
public static Object newProxyInstance(ClassLoader loader,//類加載器
Class<?>[] interfaces,//動態(tài)代理類需要實現(xiàn)的接口
InvocationHandler h) //調(diào)用處理器
throws IllegalArgumentException
{
...
}</pre>
此方法的三個參數(shù)介紹如下:
第一個參數(shù)為ClassLoader類加載器類型,此處的類加載器和被委托類的類加載器相同即可。
第二個參數(shù)為Class[]類型,代表動態(tài)代理類將會實現(xiàn)的抽象接口,此接口是被委托類所實現(xiàn)的接口。
第三個參數(shù)為InvocationHandler類型,它的調(diào)用處理器實例將作為JDK生成的動態(tài)代理對象的內(nèi)部成員,在對動態(tài)代理對象進行方法調(diào)用時,該處理器的invoke(...)方法會被執(zhí)行。
InvocationHandler處理器的invoke(...)方法如何實現(xiàn)由大家自己決定。對被委托類(真實目標(biāo)類)的擴展或者定制邏輯一般都會定義在此InvocationHandler處理器的invoke(...)方法中。
JVM在調(diào)用Proxy.newProxyInstance(...)方法時會自動為動態(tài)代理對象生成一個內(nèi)部的代理類,那么是否能看到該動態(tài)代理類的class字節(jié)碼呢?
答案是肯定的,可以通過如下方式獲取其字節(jié)碼,并且保存到文件中:
/**
獲取動態(tài)代理類的class字節(jié)碼
/
byte[] classFile = ProxyGenerator.generateProxyClass("Proxy0",
RealRpcDemoClientImpl.class.getInterfaces());
/
*在當(dāng)前的工程目錄下保存文件
*/
FileOutputStream fos =new FileOutputStream(new File("Proxy0.class"));
fos.write(classFile);
fos.flush();
fos.close();</pre>
運行3.1.4節(jié)的dynamicProxyTest()測試用例,在demo-provider模塊的根路徑可以發(fā)現(xiàn)被新創(chuàng)建的Proxy0.class字節(jié)碼文件。如果IDE有反編譯的能力,就可以在IDE中打開該文件,然后可以看到其反編譯的源碼:
import com.crazymaker.demo.proxy.MockDemoClient;
import com.crazymaker.springcloud.common.result.RestOut;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class Proxy0 extends Proxy implements MockDemoClient {
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
private static Method m0;
public Proxy0(InvocationHandler var1) throws {
super(var1);
}
...
public final RestOut echo(String var1) throws {
try {
return (RestOut)super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final RestOut hello() throws {
try {
return (RestOut)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
...
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("com.crazymaker.demo.proxy.MockDemoClient")
.getMethod("echo", Class.forName("java.lang.String"));
m3 = Class.forName("com.crazymaker.demo.proxy.MockDemoClient")
.getMethod("hello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}</pre>
通過代碼可以看出,這個動態(tài)代理類其實只做了兩件簡單的事情:
(1)該動態(tài)代理類實現(xiàn)了接口類的抽象方法。動態(tài)代理類Proxy0實現(xiàn)了MockDemoClient接口的echo(String)、hello()兩個方法。此外,Proxy0還繼承了java.lang.Object的equals()、hashCode()、toString()方法。
(2)該動態(tài)代理類將對自己的方法調(diào)用委托給了InvocationHandler調(diào)用處理器內(nèi)部成員。以上代理類Proxy0的每一個方法實現(xiàn)的代碼其實非常簡單,并且邏輯大致一樣:將方法自己的Method反射對象和調(diào)用參數(shù)進行二次委托,委托給內(nèi)部成員InvocationHandler調(diào)用處理器的invoke(...)方法。至于該內(nèi)部InvocationHandler調(diào)用處理器的實例,則由大家自己編寫,在通過java.lang.reflect.Proxy的newProxyInstance(...)創(chuàng)建動態(tài)代理對象時作為第三個參數(shù)傳入。
至此,JDK動態(tài)代理機制的核心原理和動態(tài)代理類的神秘面紗已經(jīng)徹底地揭開了。
Feign的RPC客戶端正是通過JDK的動態(tài)代理機制來實現(xiàn)的,F(xiàn)eign對RPC調(diào)用的各種增強處理主要是通過調(diào)用處理器InvocationHandler來實現(xiàn)的。