金融级移动开发平台 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