在上一篇文章中啃憎,講解了反射 與動(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ò)程可以用下圖概括:
由于本人水平有限供汛,有什么問(wèn)題可以評(píng)論滞详,喜歡的可以關(guān)注。