mPaas-RPC拦截器各种场景下的使用指南
1. 背景
金融級移動開發(fā)平臺 mPaaS[1](Mobile PaaS)為 App 開發(fā)、測試、運(yùn)營及運(yùn)維提供云到端的一站式解決方案,能有效降低技術(shù)門檻、減少研發(fā)成本、提升開發(fā)效率,協(xié)助企業(yè)快速搭建穩(wěn)定高質(zhì)量的移動應(yīng)用。其中移動網(wǎng)關(guān)服務(wù)(Mobile Gateway Service,簡稱 MGS)作為mPaas最重要的組件之一,連接了移動客戶端與服務(wù)端,簡化了移動端與服務(wù)端的數(shù)據(jù)協(xié)議和通訊協(xié)議,從而能夠顯著提升開發(fā)效率和網(wǎng)絡(luò)通訊效率。在我們?nèi)粘_\(yùn)維過程中發(fā)現(xiàn),很多用戶在使用客戶端RPC組件的時候,有很多不同場景的訴求,比如攔截請求添加業(yè)務(wù)請求標(biāo)記,免登,返回結(jié)果模擬,異常處理,限流等。本文旨在介紹通過利用RPC提供的攔截器機(jī)制,通過不同實(shí)際場景的描述,供業(yè)務(wù)參考使用。
2. RPC調(diào)用原理
當(dāng) App 在移動網(wǎng)關(guān)控制臺接入后臺服務(wù)后,調(diào)用RPC的示例代碼如下:
RpcDemoClient client = MPRpc.getRpcProxy(RpcDemoClient.class);
// 設(shè)置請求
GetIdGetReq req = new GetIdGetReq();
req.id = "123";
req.age = 14;
req.isMale = true;
// 發(fā)起 rpc 請求
String response = client.getIdGet(req);
值得好奇的是,整個調(diào)用過程中其實(shí)我們并沒有去實(shí)現(xiàn) RpcDemoClient 這個接口,而是通過 MPRpc.getRpcProxy 獲取了一個代理,通過代理對象完成了調(diào)用。在這里其實(shí)主要使用了 Java 動態(tài)代理的技術(shù)。當(dāng)調(diào)用RPC接口的時候,會通過動態(tài)代理的RpcInvocationHandler,回調(diào)其實(shí)現(xiàn)的invoke方法,最終在invoke內(nèi)實(shí)現(xiàn)數(shù)據(jù)的序列化處理最后通過網(wǎng)絡(luò)庫發(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ù)開發(fā)中,如果在某些情況下需要控制客戶端的網(wǎng)絡(luò)請求(攔截網(wǎng)絡(luò)請求,禁止訪問某些接口,或者限流),可以通過 RPC 攔截器實(shí)現(xiàn)。
RpcService rpcService =
getMicroApplicationContext().findServiceByInterface(RpcService.class.getName());
rpcService.addRpcInterceptor(OperationType.class, new CommonInterceptor());
3. 攔截器
RPC目前采用了攔截器機(jī)制實(shí)現(xiàn)RPC的自定義處理,如下圖所示,業(yè)務(wù)可以通過設(shè)置自定義RpcIntercept實(shí)現(xiàn)在請求前,請求異常,請求返回三個階段對RPC的定制處理。
圖1:rpc攔截器調(diào)用示意圖
4. preHandle場景
典型使用場景:業(yè)務(wù)添加自定義的業(yè)務(wù)全局標(biāo)識或者其他統(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;}
典型使用場景:比如如果當(dāng)前未登錄,對需要登錄的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)前未登錄,請登錄", 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();}
5. postHandle場景
1. 攔截接口返回
典型使用場景:全局修改服務(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...// 場景:修改服務(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();}
6. exceptionHandle場景
1. 異常統(tǒng)一處理
比如登錄態(tài)失效,服務(wù)端會統(tǒng)一返回2000的錯誤碼,客戶端可以在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ā)請求,免登操作對上層業(yè)務(wù)無感知try {
LoggerFactory.getTraceLogger().debug(TAG, "exceptionHandle. Start resend rpc begin " + operationType);// 重發(fā)請求
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;}
7. H5場景
由于H5場景中使用的jsapi的rpc,需要支持在js環(huán)境里傳遞到native環(huán)境,所以在設(shè)計(jì)上,是統(tǒng)一通過operationType: alipay.client.executerpc 接口進(jìn)行的轉(zhuǎn)發(fā),所以針對H5發(fā)送的RPC請求,需要做特殊判斷,通過入?yún)⒛玫秸鎸?shí)的operationType接口,示例代碼如下。
1. 獲取H5請求的接口名稱和入?yún)?/font>
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ù)通過jsapi去請求rpc,如何獲取jsapi請求的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)?br />String req = (String) objects[1];
LoggerFactory.getTraceLogger().debug(TAG, "operationType:" + rpcName + " " + req);} else {// Native的rpc}
LoggerFactory.getTraceLogger().debug(TAG, "operationType:" + operationType);return true;}
參考文檔
[1] mPaaS平臺:https://www.aliyun.com/product/mobilepaas/mpaas
我們是阿里云智能全球技術(shù)服務(wù)-SRE團(tuán)隊(duì),我們致力成為一個以技術(shù)為基礎(chǔ)、面向服務(wù)、保障業(yè)務(wù)系統(tǒng)高可用的工程師團(tuán)隊(duì);提供專業(yè)、體系化的SRE服務(wù),幫助廣大客戶更好地使用云、基于云構(gòu)建更加穩(wěn)定可靠的業(yè)務(wù)系統(tǒng),提升業(yè)務(wù)穩(wěn)定性。我們期望能夠分享更多幫助企業(yè)客戶上云、用好云,讓客戶云上業(yè)務(wù)運(yùn)行更加穩(wěn)定可靠的技術(shù)。
原文鏈接:https://developer.aliyun.com/article/781269?
版權(quán)聲明:本文內(nèi)容由阿里云實(shí)名注冊用戶自發(fā)貢獻(xiàn),版權(quán)歸原作者所有,阿里云開發(fā)者社區(qū)不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。具體規(guī)則請查看《阿里云開發(fā)者社區(qū)用戶服務(wù)協(xié)議》和《阿里云開發(fā)者社區(qū)知識產(chǎn)權(quán)保護(hù)指引》。如果您發(fā)現(xiàn)本社區(qū)中有涉嫌抄襲的內(nèi)容,填寫侵權(quán)投訴表單進(jìn)行舉報(bào),一經(jīng)查實(shí),本社區(qū)將立刻刪除涉嫌侵權(quán)內(nèi)容。總結(jié)
以上是生活随笔為你收集整理的mPaas-RPC拦截器各种场景下的使用指南的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何跑通第一个 SQL 作业
- 下一篇: 使用 CoreDNS sidecar 来