代理 代理的意思就是請求者不直接與終端交互,而是通過一個中間者來做請求轉發,舉幾個生活中的代理案例: 翻牆上網 國外有很多網站在國內不能訪問,所以就需要利用一個代理中轉發請求,達到瞞天過海的目的。 用戶炒股 要想投資股票,你只能通過在證券商那開通賬戶然後,通過在證券商那提供的功能才能實現在上海證券交 ...
代理
代理的意思就是請求者不直接與終端交互,而是通過一個中間者來做請求轉發,舉幾個生活中的代理案例:
- FQ上網
國外有很多網站在國內不能訪問,所以就需要利用一個代理中轉發請求,達到瞞天過海的目的。
- 用戶炒股
要想投資股票,你只能通過在證券商那開通賬戶然後,通過在證券商那提供的功能才能實現在上海證券交易所交易股票。
- 經濟人
下麵的看圖就明白了,不需要多做解釋。
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
文中代碼是依賴上述項目的,如果有不明白的可下載源碼