Java基礎:動態代理在RPC框架中應用
阿新 • • 發佈:2019-01-26
RPC,遠端過程呼叫。就是呼叫遠端機器上的方法。
原理其實很簡單,就是客戶端上執行的程式在呼叫物件方法時,底層將針對該方法的呼叫轉換為TCP/HTTP請求,傳送到遠端伺服器,遠端伺服器監聽固定埠,收到這個TCP/HTTP請求後會解析出相關資訊,包括客戶端想要呼叫哪個類的哪個方法,引數是什麼等,然後進行對應的呼叫,將呼叫結果再通過資料包發回即可。
RPC中一般會有一些“契約”的概念,即客戶端和服務端雙方約定好的介面,表明服務端實現了哪些介面,客戶端可以使用這些介面。下面以一個我實現的簡單的RPC框架為例說明。
一、客戶端
客戶端一般會建立一個服務訪問代理,其實就是這個契約介面型別的物件,下面是一個建立服務代理的例子:
//建立代理,呼叫服務
IHelloService proxy= (IHelloService)RpcProxyFactory.createProxy(IHelloService.class,"HelloService", 3000);
System.out.println("服務呼叫結果:"+proxy.sayHello("季義欽"));
其中createProxy介面的實現如下:
/** * 建立遠端呼叫代理 * @paramobjClass 介面類物件 * @paramserviceName 服務名稱 * @paramURL 服務地址 * @paramtimeout 連線超時時限 * @return * @throws Exception */ publicstatic Object createProxy(Class<?> objClass, String serviceName, StringURL, int timeout) throws Exception { //解析呼叫地址 String[]urls = URL.split(":"); StringipAddress = urls[1]; intport =Integer.parseInt(urls[2]); //建立InvocationHandler CBIPInvocationHandlerhandler = new CBIPInvocationHandler(ipAddress, port, timeout); handler.setServiceName(serviceName); //返回代理 returnProxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{objClass}, handler); }
很明顯可以看到藉助的是JavaInvocationHandler動態代理機制,通過Proxy靜態方法newProxyInstance返回服務代理。
其中CBIPInvocationHandler是客戶端程式碼的核心,是實現了InvocationHandler介面的Java動態代理,其invoke方法實現如下:
/** * 所有方法呼叫都通過invoke來執行 */ publicObject invoke(Object proxy, Method method, Object[] args) throws Throwable { Objectresult = null; try { //獲取呼叫相關資訊 StringbeanName = this.serviceName; //this.targrtObject.getClass().getSimpleName(); StringmethodName = method.getName(); String[]argTypes = createParamSignature(method.getParameterTypes()); //發起遠端方法同步呼叫,傳遞呼叫資訊 result = invokeSync(beanName,methodName, argTypes, args); } catch(Exceptionee) { ee.printStackTrace(); returnnull; } returnresult; }
invokeSync方法作用就是將呼叫相關資訊通過Mina框架(JavaNIO框架,非同步事件驅動,提供TCP/UDP通訊的高層抽象API)傳送出去。
二、服務端
通過Mina框架實現的服務端程式獲取來自客戶端的方法呼叫的請求資料包後,解析出呼叫相關資訊。
/**
* 收到來自客戶端的訊息
*/
@Override
public void messageReceived(IoSessionsession, Object message) throws Exception {
if(message instanceofCbipTcpRequest)
{
CbipTcpRequestrequest = (CbipTcpRequest)message;
try
{
//讀取spring配置檔案根據服務名稱獲取服務全限定名,然後反射出目標例項
ApplicationContextctx = new ClassPathXmlApplicationContext("dsf_config.xml");
ServiceConfigservice = (ServiceConfig)ctx.getBean(request.getBeanName());
Class<?>objClass = Class.forName(service.getServiceName());
Objectobj = objClass.newInstance();
//呼叫指定的方法
CBIPSkeletonskeleton = new CBIPSkeleton(obj, obj.getClass());
Objectresult = skeleton.invoke(request.getMethodName(), request.getArgs());
//回覆
CbipTcpResponse response= new CbipTcpResponse();
response.setRequestID(request.getId()); // 請求ID
response.setBeanName(request.getBeanName());
response.setMethodName(request.getMethodName());
response.setResult(result);
session.write(response); //類似套接字
}catch(Exceptionee)
{
ee.printStackTrace();
}
}
}
這個資料包接收函式主要三個進行步驟:
(1)通過收到的客戶端請求知道呼叫哪個類,從配置檔案中讀取該類的全限定名載入到JVM,反射出該類例項;
(2)通過反射出來的例項進行方法呼叫;
(3)通過Mina框架返回呼叫結果;