簡易RPC框架-代理


代理

代理的意思就是請求者不直接與終端交互,而是通過一個中間者來做請求轉發,舉幾個生活中的代理案例:

  • 翻牆上網

國外有很多網站在國內不能訪問,所以就需要利用一個代理中轉發請求,達到瞞天過海的目的。

  • 用戶炒股

要想投資股票,你只能通過在證券商那開通賬戶然后,通過在證券商那提供的功能才能實現在上海證券交易所交易股票。

  • 經濟人

下面的看圖就明白了,不需要多做解釋。

RPC

RPC的意思是遠程過程調用,使客戶端調用遠程方法像調用本地方法一樣簡單,而不需要去關心如何與遠程服務器通信相關問題:

  • 具體的通信協議選擇

比如是TCP通信還是基於HTTP通信,像dubbo默認是基於TCP的,當當在此基礎上擴展了劫持HTTP通信的擴展,spring cloud也是基於HTTP。

  • 具體的編碼方式

計算機之間通信時最需要按一定格式的數據進行傳輸,比如TCP通信時就需要將JAVA對象通過編碼轉換成字節流,比如這兩對象:
MessageToByteEncoder與ByteToMessageDecoder

  • 具體數據傳輸

比如TCP傳輸時,各類問題:半包,粘包,延遲,超時,重連等處理。

  • ......

為了不在客戶端調用服務端時處理上述邏輯,就需要有一個專門處理上述問題的框架來協助,這里可以利用JAVA提供的動態代理業完成。將請求委托給一個代理,這個代理去專門解決通信問題。

動態代理基礎

InvocationHandler

要想寫一個代理實現類,最簡單的方法就是實現InvocationHandler接口,它只包含一個接口方法:

Object invoke(Object proxy, Method method, Object[] args)

包含三個參數:

  • proxy

是指被代理的真實對象

  • method

是指我們需要執行的真實對象的方法

  • args

是指我們需要執行的真實對象的方法所需要的參數

此類需要配合下面的Proxy類來創建動態代理,自身只是一個代理類的實現。

Proxy

這個類是用來創建真實對象代理類的,我這里應用Proxy.newProxyInstance構建Rpc客戶端代理,它也有三個參數:

  • ClassLoader loader

是指由哪一個類加載器來加載生成的代理對象,一般我們就用真實對象所用的加載器即可。

  • Class<?>[] interfaces

是指真實對象都實現了哪些接口,接口確認之后才能調用其中的方法。

  • InvocationHandler h

是指產生的代理類在執行方法時所關聯的一個代理對象,即我們第一步提到的實現了InvocationHandler接口的實例,代理對象在執行方法時委托給這個關聯的handle去處理。

RPC客戶端代理實現

RpcProxy

編寫一個代理類RpcProxy,它用來處理TCP通信相關的問題,主要流程如下:

  • 組裝參數

從method以及args參數中獲取相應的值,填充到私有協議棧所需要的數據對象中(RpcRequest)。

  • 找一個可用的連接進行數據通信

從連接管理器(RpcClientInvokerManager)中獲取可用連接,構建處理請求鏈最后調用執行方法。

  • 返回結果

根據客戶端請求的方式,如果是需要返回值則返回一個Future對象供異步回調,如果不關心返回值則直接返回空。

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        RpcRequest request = new RpcRequest();
        request.setRequestId(UUID.randomUUID().toString());
        request.setClassName(method.getDeclaringClass().getName());
        request.setMethodName(method.getName());
        request.setParameterTypes(method.getParameterTypes());
        request.setParameters(args);

        if (this.reference != null) {
            request.setMaxExecutesCount(this.reference.maxExecutesCount());
        }

        request.setContextParameters(RpcContext.getContext().getContextParameters());

        RpcClientInvoker invoker = RpcClientInvokerManager.getInstance(this.referenceConfig).getInvoker();
        invoker.setRpcRequest(request);

        RpcInvoker rpcInvoker=invoker.buildInvokerChain(invoker);
        ResponseFuture response=(ResponseFuture) rpcInvoker.invoke(invoker.buildRpcInvocation(request));

        if(isSync){
            return response.get();
        }
        else {
            RpcContext.getContext().setResponseFuture(response);
            return null;
        }
    }

RPC客戶端初始化遠程接口

  • 在RpcClient類中封裝一個創建代理的方法:
public <T> T createProxy(Class<T> interfaceClass,RpcReference reference) {
    return (T) Proxy.newProxyInstance(
            interfaceClass.getClassLoader(),
            new Class<?>[]{interfaceClass},
            new RpcProxy<T>(interfaceClass,this.referenceConfig,reference)
    );
}

通過上面代碼產后的代理,是在JVM運行時產生的,它即不是我們上面所提到的代理對象RpcProxy也不是我們的真實對象,它的主要作用就是在調用接口時,將invoke方法的執行委托給我們的代理對象RpcProxy,起到一個轉發的效果。

  • 通過注解自動生成代理

要想實現調用遠程接口與調用本地接口一樣簡單,思路就是在系統初始化時,掃描特殊注解的變量從而為變量創建代理對象。這里可以借助於BeanPostProcessor對象,它有一個初始化的方法:

public Object postProcessBeforeInitialization(Object bean, String beanName)

我們可以在這個函數中為特殊的變量調用RpcClient.createProxy生成代理,比如下面的代碼會遍歷所有的字段,如果字段上標記了RpcReference注解,說明這是一個遠程接口,所以調用RpcClient調用createProxy生成代理對象。

public Object initRpcReferenceBean(Object bean, String beanName){
    Class<?> clazz = bean.getClass();
    if(isProxyBean(bean)){
        clazz = AopUtils.getTargetClass(bean);
    }

    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        try {
            if (! field.isAccessible()) {
                field.setAccessible(true);
            }
            RpcReference reference = field.getAnnotation(RpcReference.class);
            if (reference != null) {
                Object value=this.rpcClient.createProxy(field.getType(),reference);
                if (value != null) {
                    field.set(bean, value);
                }
            }
        } catch (Exception e) {
            throw new BeanInitializationException("Failed to init remote service reference at filed " + field.getName() + " in class " + bean.getClass().getName(), e);
        }
    }
    return bean;
}

RPC客戶端引用遠程接口

以下代碼是一個服務中依賴的變量,增加上@rpcreference之后說明接口是一個遠程接口。

@RpcReference(isSync = false)
private ProductService productServiceAsync;

方法中調用遠程接口:

public Product getById(Long productId){
   return this.productService.getById(productId);
}

業務代碼中,直接調用productServiceAsync中包含的方法即可,不需要去寫任何寫通信相關的代碼,實現了典型的遠程過程調用。

本文源碼

https://github.com/jiangmin168168/jim-framework

文中代碼是依賴上述項目的,如果有不明白的可下載源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM