mPaaS-RPC 拦截器各种场景下的使用指南
簡(jiǎn)介:?RPC攔截器機(jī)制在preHandle、postHandle、exceptionHandle以及H5等場(chǎng)景中的應(yīng)用
?
mPaaS 移動(dòng)網(wǎng)關(guān)服務(wù)(Mobile Gateway Service,簡(jiǎn)稱 MGS)作為 mPaas 最重要的組件之一,連接了移動(dòng)客戶端與服務(wù)端,簡(jiǎn)化了移動(dòng)端與服務(wù)端的數(shù)據(jù)協(xié)議和通訊協(xié)議,從而能夠顯著提升開(kāi)發(fā)效率和網(wǎng)絡(luò)通訊效率。
在我們?nèi)粘_\(yùn)維過(guò)程中發(fā)現(xiàn),很多用戶在使用客戶端RPC組件的時(shí)候,有很多不同場(chǎng)景的訴求,比如攔截請(qǐng)求添加業(yè)務(wù)請(qǐng)求標(biāo)記,免登,返回結(jié)果模擬,異常處理,限流等。
本文旨在介紹通過(guò)利用RPC提供的攔截器機(jī)制,通過(guò)不同實(shí)際場(chǎng)景的描述,供業(yè)務(wù)參考使用。
RPC調(diào)用原理
當(dāng) App 在移動(dòng)網(wǎng)關(guān)控制臺(tái)接入后臺(tái)服務(wù)后,調(diào)用RPC的示例代碼如下:
RpcDemoClient client = MPRpc.getRpcProxy(RpcDemoClient.class); // 設(shè)置請(qǐng)求 GetIdGetReq req = new GetIdGetReq(); req.id = "123"; req.age = 14; req.isMale = true; // 發(fā)起 rpc 請(qǐng)求 String response = client.getIdGet(req);值得好奇的是,整個(gè)調(diào)用過(guò)程中其實(shí)我們并沒(méi)有去實(shí)現(xiàn) RpcDemoClient 這個(gè)接口,而是通過(guò) MPRpc.getRpcProxy 獲取了一個(gè)代理,通過(guò)代理對(duì)象完成了調(diào)用。在這里其實(shí)主要使用了 Java 動(dòng)態(tài)代理的技術(shù)。
如下圖所示,當(dāng)調(diào)用RPC接口的時(shí)候,會(huì)通過(guò)動(dòng)態(tài)代理的RpcInvocationHandler,回調(diào)其實(shí)現(xiàn)的invoke方法,最終在invoke內(nèi)實(shí)現(xiàn)數(shù)據(jù)的序列化處理最后通過(guò)網(wǎng)絡(luò)庫(kù)發(fā)到服務(wù)端。
public <T> T getRpcProxy(Class<T> clazz) {LogCatUtil.info("RpcFactory", "clazz=[" + clazz.getName() + "]");return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new RpcInvocationHandler(this.mConfig, clazz, this.mRpcInvoker));}在業(yè)務(wù)開(kāi)發(fā)中,如果在某些情況下需要控制客戶端的網(wǎng)絡(luò)請(qǐng)求(攔截網(wǎng)絡(luò)請(qǐng)求,禁止訪問(wèn)某些接口,或者限流),可以通過(guò) RPC 攔截器實(shí)現(xiàn)。
RpcService rpcService = getMicroApplicationContext().findServiceByInterface(RpcService.class.getName());rpcService.addRpcInterceptor(OperationType.class, new CommonInterceptor());攔截器原理
RPC目前采用了攔截器機(jī)制實(shí)現(xiàn)RPC的自定義處理,如下圖所示,業(yè)務(wù)可以通過(guò)設(shè)置自定義RpcIntercept實(shí)現(xiàn)在請(qǐng)求前,請(qǐng)求異常,請(qǐng)求返回三個(gè)階段對(duì)RPC的定制處理。
?
preHandle場(chǎng)景
1.全局添加業(yè)務(wù)自定義請(qǐng)求header
典型使用場(chǎng)景:業(yè)務(wù)添加自定義的業(yè)務(wù)全局標(biāo)識(shí)或者其他統(tǒng)計(jì)字段。
@Overridepublic boolean preHandle(Object proxy,ThreadLocal<Object> retValue,byte[] retRawValue,Class<?> aClass,Method method,Object[] args,Annotation annotation,ThreadLocal<Map<String, Object>> threadLocal)throws RpcException {//Do something...RpcInvocationHandler handler = (RpcInvocationHandler) Proxy.getInvocationHandler(proxy);handler.getRpcInvokeContext().addRequestHeader("header", "headerCustom");return true;}2.阻斷當(dāng)前請(qǐng)求rpc流程
典型使用場(chǎng)景:比如如果當(dāng)前未登錄,對(duì)需要登錄的rpc先阻斷,統(tǒng)一提示登錄。
@Overridepublic boolean preHandle(Object proxy,ThreadLocal<Object> retValue,byte[] retRawValue,Class<?> aClass,Method method,Object[] args,Annotation annotation,ThreadLocal<Map<String, Object>> threadLocal)throws RpcException {//Do something...String operationType = getOperationType(aClass, method, args);if ("operationType1".equals(operationType)) {boolean isLogin = false;if (!isLogin) {Handler handler = new Handler(Looper.getMainLooper());handler.post(new Runnable() {@Overridepublic void run() {Toast.makeText(LauncherApplicationAgent.getInstance().getApplicationContext(),"當(dāng)前未登錄,請(qǐng)登錄", Toast.LENGTH_SHORT).show();}});// 返回給上層調(diào)用登錄失敗的異常,上層做業(yè)務(wù)處理throw new RpcException(RpcException.ErrorCode.CLIENT_LOGIN_FAIL_ERROR, "login fail.");}}return true;}private String getOperationType(Class<?> aClass, Method method, Object[] args) {if (aClass == null || null == method) return "";OperationType operationType = method.getAnnotation(OperationType.class);return operationType == null ? "" : operationType.value();}postHandle場(chǎng)景
1.攔截接口返回
典型使用場(chǎng)景:全局修改服務(wù)端的返回結(jié)果,比如mock服務(wù)端的數(shù)據(jù)。
@Overridepublic boolean postHandle(Object proxy,ThreadLocal<Object> threadLocal,byte[] retRawValue,Class<?> aClass,Method method,Object[] args,Annotation annotation) throws RpcException {//Do something...// 場(chǎng)景:修改服務(wù)端返回的數(shù)據(jù),比如mock數(shù)據(jù),或者修改服務(wù)端數(shù)據(jù)String operationType = getOperationType(aClass, method, args);LoggerFactory.getTraceLogger().debug(TAG, "postHandle:" + operationType);if ("operationType1".equals(operationType)) {String value = JSON.parse(retRawValue).toString();LoggerFactory.getTraceLogger().debug(TAG, "postHandle 原始返回" + value);String mockData = "{\"img\":\"imgPath\",\"User\":{\"name\":\"我是mock的數(shù)據(jù)\",\"age\":18}}";Object mockObj = JSON.parseObject(mockData, method.getReturnType());threadLocal.set(mockObj);return true;}return true;}private String getOperationType(Class<?> aClass, Method method, Object[] args) {if (aClass == null || null == method) return "";OperationType operationType = method.getAnnotation(OperationType.class);return operationType == null ? "" : operationType.value();}exceptionHandle場(chǎng)景
1.異常統(tǒng)一處理
比如登錄態(tài)失效,服務(wù)端會(huì)統(tǒng)一返回2000的錯(cuò)誤碼,客戶端可以在exceptionHandle里統(tǒng)一攔截進(jìn)行登錄態(tài)免登操作。
@Overridepublic boolean exceptionHandle(Object proxy, ThreadLocal<Object> retValue, byte[] bytes, Class<?> aClass, Method method, Object[] objects,RpcException rpcException, Annotation annotation) throws RpcException {String operationType = getOperationType(aClass, method, objects);if (RpcException.ErrorCode.CLIENT_LOGIN_FAIL_ERROR == rpcException.getCode()&& "operationType1".equals(operationType)) {// 1. 去免登hasLogin = true;// 2. 免登后在幫上層重發(fā)請(qǐng)求,免登操作對(duì)上層業(yè)務(wù)無(wú)感知try {LoggerFactory.getTraceLogger().debug(TAG, "exceptionHandle. Start resend rpc begin " + operationType);// 重發(fā)請(qǐng)求Object object = method.invoke(proxy, objects);retValue.set(object);LoggerFactory.getTraceLogger().debug(TAG, "exceptionHandle. Start resend rpc success");return false;} catch (Throwable e) {LoggerFactory.getTraceLogger().error(TAG, "resend rpc occurs illegal argument exception", e);throw new RpcException(RpcException.ErrorCode.CLIENT_HANDLE_ERROR, e + "");}}return true;}H5場(chǎng)景
由于H5場(chǎng)景中使用的jsapi的rpc,需要支持在js環(huán)境里傳遞到native環(huán)境,所以在設(shè)計(jì)上,是統(tǒng)一通過(guò)operationType: alipay.client.executerpc 接口進(jìn)行的轉(zhuǎn)發(fā),所以針對(duì)H5發(fā)送的RPC請(qǐng)求,需要做特殊判斷,通過(guò)入?yún)⒛玫秸鎸?shí)的operationType接口,示例代碼如下。
1.獲取H5請(qǐng)求的接口名稱和入?yún)?/strong>
var params = [{"_requestBody":{"userName":"", "userId":0} }] var operationType = 'alipay.mobile.ic.dispatch' AlipayJSBridge.call('rpc', {operationType: operationType,requestData: params,headers:{} }, function (result) {console.log(result); });如上圖所示,業(yè)務(wù)通過(guò)jsapi去請(qǐng)求rpc,如何獲取jsapi請(qǐng)求的rpc名稱,可以參考代碼如下:
@Overridepublic boolean preHandle(Object o, ThreadLocal<Object> threadLocal, byte[] bytes, Class<?> aClass, Method method, Object[] objects, Annotation annotation, ThreadLocal<Map<String, Object>> threadLocal1) throws RpcException {String operationType = getOperationType(aClass, method, objects);if ("alipay.client.executerpc".equals(operationType)) {// H5的rpc名稱String rpcName = (String) objects[0];// 入?yún)tring req = (String) objects[1];LoggerFactory.getTraceLogger().debug(TAG, "operationType:" + rpcName + " " + req);} else {// Native的rpc}LoggerFactory.getTraceLogger().debug(TAG, "operationType:" + operationType);return true;}?
原文鏈接
本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的mPaaS-RPC 拦截器各种场景下的使用指南的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何成为云原生时代的卓越架构师
- 下一篇: 蚂蚁构建服务演进史