金融級移動開發平臺 mPaaS(Mobile PaaS)爲 App 開發、測試、運營及運維提供雲到端的一站式解決方案,能有效下降技術門檻、減小研發成本、提高開發效率,協助企業快速搭建穩定高質量的移動應用。其中移動網關服務(Mobile Gateway Service,簡稱 MGS)做爲mPaas最重要的組件之一,鏈接了移動客戶端與服務端,簡化了移動端與服務端的數據協議和通信協議,從而可以顯著提高開發效率和網絡通信效率。在咱們平常運維過程當中發現,不少用戶在使用客戶端RPC組件的時候,有不少不一樣場景的訴求,好比攔截請求添加業務請求標記,免登,返回結果模擬,異常處理,限流等。本文旨在介紹經過利用RPC提供的攔截器機制,經過不一樣實際場景的描述,供業務參考使用。java
當 App 在移動網關控制檯接入後臺服務後,調用RPC的示例代碼以下:api
RpcDemoClient client = MPRpc.getRpcProxy(RpcDemoClient.class); // 設置請求 GetIdGetReq req = new GetIdGetReq(); req.id = "123"; req.age = 14; req.isMale = true; // 發起 rpc 請求 String response = client.getIdGet(req);
值得好奇的是,整個調用過程當中其實咱們並無去實現 RpcDemoClient 這個接口,而是經過 MPRpc.getRpcProxy 獲取了一個代理,經過代理對象完成了調用。在這裏其實主要使用了 Java 動態代理的技術。以下圖所示,當調用RPC接口的時候,會經過動態代理的RpcInvocationHandler,回調其實現的invoke方法,最終在invoke內實現數據的序列化處理最後經過網絡庫發到服務端。
網絡
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)); }
在業務開發中,若是在某些狀況下須要控制客戶端的網絡請求(攔截網絡請求,禁止訪問某些接口,或者限流),能夠經過 RPC 攔截器實現。運維
RpcService rpcService = getMicroApplicationContext().findServiceByInterface(RpcService.class.getName()); rpcService.addRpcInterceptor(OperationType.class, new CommonInterceptor());
三 攔截器
ide
RPC目前採用了攔截器機制實現RPC的自定義處理,以下圖所示,業務能夠經過設置自定義RpcIntercept實如今請求前,請求異常,請求返回三個階段對RPC的定製處理。oop
典型使用場景:業務添加自定義的業務全局標識或者其餘統計字段post
1 @Override 2 public boolean preHandle(Object proxy, 3 ThreadLocal<Object> retValue, 4 byte[] retRawValue, 5 Class<?> aClass, 6 Method method, 7 Object[] args, 8 Annotation annotation, 9 ThreadLocal<Map<String, Object>> threadLocal) 10 throws RpcException { 11 //Do something... 12 RpcInvocationHandler handler = (RpcInvocationHandler) Proxy.getInvocationHandler(proxy); 13 handler.getRpcInvokeContext().addRequestHeader("header", "headerCustom"); 14 return true; 15 }
典型使用場景:好比若是當前未登陸,對須要登陸的rpc先阻斷,統一提示登陸測試
1 @Override 2 public boolean preHandle(Object proxy, 3 ThreadLocal<Object> retValue, 4 byte[] retRawValue, 5 Class<?> aClass, 6 Method method, 7 Object[] args, 8 Annotation annotation, 9 ThreadLocal<Map<String, Object>> threadLocal) 10 throws RpcException { 11 //Do something... 12 String operationType = getOperationType(aClass, method, args); 13 if ("operationType1".equals(operationType)) { 14 boolean isLogin = false; 15 if (!isLogin) { 16 Handler handler = new Handler(Looper.getMainLooper()); 17 handler.post(new Runnable() { 18 @Override 19 public void run() { 20 Toast.makeText(LauncherApplicationAgent.getInstance().getApplicationContext(), 21 "當前未登陸,請登陸", Toast.LENGTH_SHORT).show(); 22 } 23 }); 24 // 返回給上層調用登陸失敗的異常,上層作業務處理 25 throw new RpcException(RpcException.ErrorCode.CLIENT_LOGIN_FAIL_ERROR, "login fail."); 26 } 27 } 28 return true; 29 } 30 private String getOperationType(Class<?> aClass, Method method, Object[] args) { 31 if (aClass == null || null == method) return ""; 32 OperationType operationType = method.getAnnotation(OperationType.class); 33 return operationType == null ? "" : operationType.value(); 34 }
典型使用場景:全局修改服務端的返回結果,好比mock服務端的數據this
@Override 2 public boolean postHandle(Object proxy, 3 ThreadLocal<Object> threadLocal, 4 byte[] retRawValue, 5 Class<?> aClass, 6 Method method, 7 Object[] args, 8 Annotation annotation) throws RpcException { 9 //Do something... 10 // 場景:修改服務端返回的數據,好比mock數據,或者修改服務端數據 11 String operationType = getOperationType(aClass, method, args); 12 LoggerFactory.getTraceLogger().debug(TAG, "postHandle:" + operationType); 13 if ("operationType1".equals(operationType)) { 14 String value = JSON.parse(retRawValue).toString(); 15 LoggerFactory.getTraceLogger().debug(TAG, "postHandle 原始返回" + value); 16 String mockData = "{\"img\":\"imgPath\",\"User\":{\"name\":\"我是mock的數據\",\"age\":18}}"; 17 Object mockObj = JSON.parseObject(mockData, method.getReturnType()); 18 threadLocal.set(mockObj); 19 return true; 20 } 21 return true; 22 } 23 24 private String getOperationType(Class<?> aClass, Method method, Object[] args) { 25 if (aClass == null || null == method) return ""; 26 OperationType operationType = method.getAnnotation(OperationType.class); 27 return operationType == null ? "" : operationType.value(); 28 }
六 exceptionHandle場景
好比登陸態失效,服務端會統一返回2000的錯誤碼,客戶端能夠在exceptionHandle裏統一攔截進行登陸態免登操做spa
1 @Override 2 public boolean exceptionHandle(Object proxy, ThreadLocal<Object> retValue, byte[] bytes, Class<?> aClass, Method method, Object[] objects, 3 RpcException rpcException, Annotation annotation) throws RpcException { 4 String operationType = getOperationType(aClass, method, objects); 5 if (RpcException.ErrorCode.CLIENT_LOGIN_FAIL_ERROR == rpcException.getCode() 6 && "operationType1".equals(operationType)) { 7 // 1. 去免登 8 hasLogin = true; 9 // 2. 免登後在幫上層重發請求,免登操做對上層業務無感知 10 try { 11 LoggerFactory.getTraceLogger().debug(TAG, "exceptionHandle. Start resend rpc begin " + operationType); 12 // 重發請求 13 Object object = method.invoke(proxy, objects); 14 retValue.set(object); 15 LoggerFactory.getTraceLogger().debug(TAG, "exceptionHandle. Start resend rpc success"); 16 return false; 17 } catch (Throwable e) { 18 LoggerFactory.getTraceLogger().error(TAG, "resend rpc occurs illegal argument exception", e); 19 throw new RpcException(RpcException.ErrorCode.CLIENT_HANDLE_ERROR, e + ""); 20 } 21 } 22 return true; 23 }
因爲H5場景中使用的jsapi的rpc,須要支持在js環境裏傳遞到native環境,因此在設計上,是統一經過operationType: alipay.client.executerpc 接口進行的轉發,因此針對H5發送的RPC請求,須要作特殊判斷,經過入參拿到真實的operationType接口,示例代碼以下。
1 var params = [{ 2 "_requestBody":{"userName":"", "userId":0} 3 }] 4 var operationType = 'alipay.mobile.ic.dispatch' 5 AlipayJSBridge.call('rpc', { 6 operationType: operationType, 7 requestData: params, 8 headers:{} 9 }, function (result) { 10 console.log(result); 11 });
如上圖所示,業務經過jsapi去請求rpc,如何獲取jsapi請求的rpc名稱,能夠參考代碼以下
1 @Override 2 public boolean preHandle(Object o, ThreadLocal<Object> threadLocal, byte[] bytes, Class<?> aClass, Method method, Object[] objects, Annotation annotation, ThreadLocal<Map<String, Object>> threadLocal1) throws RpcException { 3 String operationType = getOperationType(aClass, method, objects); 4 if ("alipay.client.executerpc".equals(operationType)) { 5 // H5的rpc名稱 6 String rpcName = (String) objects[0]; 7 // 入參 8 String req = (String) objects[1]; 9 LoggerFactory.getTraceLogger().debug(TAG, "operationType:" + rpcName + " " + req); 10 11 } else { 12 // Native的rpc 13 } 14 LoggerFactory.getTraceLogger().debug(TAG, "operationType:" + operationType); 15 return true; 16 } 17