1. 程式人生 > >Java基礎:動態代理在RPC框架中應用

Java基礎:動態代理在RPC框架中應用


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框架返回呼叫結果;