反射加載SDK完成統一調用

文章目錄

  • 1、需求背景
  • 2、接口+抽象類+具體實現類
  • 3、疑問
  • 4、存在的問題
  • 5、通過反射加載SDK并完成調用
  • 5、補充:關于業務網關
  • 7、補充:關于SDK的開發

關鍵點:

  • 接口+抽象類(半抽象半實現)+具體實現類
  • 業務網關
  • 反射加載SDK,完成統一調用

在這里插入圖片描述

半路接手一個需求,需要從自己系統出發,經過業務網關的統一校驗和轉發,來請求第三方供應商系統的接口,整理下看同事代碼學到的一點思路。

1、需求背景

第三方供應商需要上架自己的產品到公司的交易平臺,但用戶使用產品時,最后一步請求的自然是供應商自己的服務器資源和API。關于這個需求的實現思路,大致是在交易平臺需要做接口有效性校驗、服務實例有效性校驗等,以及消費數據記錄落庫,最后轉發到供應商接口去請求資源(既然是請求別人的系統,那就涉及到怎么通過人家的鑒權系統)。

@PostMapping("/data/{platform}/{apiId}")
public Object redirect(@PathVariable String platform, @PathVariable String apiId,@RequestBody Map<String, Object> parameterMap, HttpServletRequest request) {//直接把API的ID放進請求參數里,后面用完了,再調三方API時去掉就行parameterMap.put("apiId", apiId);return redirectHandler.redirect(platform, ServletUtils.getHeaders(request), parameterMap);
}

這里給訪問所有三方系統接口一個統一的入口,做為業務網關(后面展開說),接口傳參中:

  • paltform:確定是哪個第三方系統
  • apiID:用來標識想請求第三方系統哪個的API接口,通過這個ID,可以在庫里查到API的路徑、三方系統的host、密鑰、以及后面會提到的SDK的存儲路徑、SDK里的核心方法名等信息
  • parameterMap:用戶傳入的請求參數
  • request:Http請求對象

其中用工具類獲取下HTTP請求的全部請求頭信息存入Map。

public class ServletUtils{/*** 獲取Http請求的請求頭信息*/public static Map<String, String> getHeaders(HttpServletRequest request) {Map<String, String> map = new LinkedHashMap<>();Enumeration<String> enumeration = request.getHeaderNames();if (enumeration != null) {while (enumeration.hasMoreElements()) {String key = enumeration.nextElement();String value = request.getHeader(key);map.put(key, value);}}return map;}}

2、接口+抽象類+具體實現類

既然需要對接很多第三方供應商系統,去調用第三方系統的API,那就考慮定義一個接口,里面抽象出一個做鑒權、轉發的方法,對接不同的供應商系統時,去實現這個接口,然后走不同的實現。

public interface ApiRedirectHandler {/*** @param headerMap 請求頭參數Map* @param paramMap 對第三方接口的請求參數* @return 返回第三方接用調用的結果*/Object redirect(Map<String, String> headerMap, Map<String, Object> paramMap);}

前面提到,在交易平臺要做一些校驗和消費記錄落庫的操作,這些是對接所有三方系統的公共步驟,而后面請求第三方系統接口肯定要做的鑒權認證以及轉發或者調用,則屬于各個三方系統的定制化行為(因為一個系統有一個系統的認證方式,A系統用APP密鑰、B系統可能用sign驗簽)。因此,考慮在接口下面墊一個抽象類,抽象類中,實現接口中的轉發方法,里面做校驗、記錄落庫等操作,同時調用本抽象類自己的抽象request方法(這個方法里做第三方系統的定制化的認證和轉發或調用)。這樣,對接不同的三方系統,只需就繼承這個抽象類,實現里面的request方法,做自己的認證和轉發即可。

總結:全抽象的接口,過渡到半抽象的抽象類,抽象類中實現接口的抽象方法時,方法體中寫一部分公共邏輯 + 調用本抽象類自己的一個抽象方法B,這個抽象方法B就給以后的普通類去繼承和重寫。

@Slf4j
public abstract class AbstractRedirectHandler implements ApiRedirectHandler {//抽象類中實現接口的方法@Overridepublic Object redirect(Map<String, String> headerMap, Map<String, Object> paramMap) {//todo: 1.請求有效性驗證//從請求參數paramMap中拿到你要調用APIId,然后查到的三方系統接口的路徑、host等信息ApiInfo  apiDetailVo = queryApiInfo(paramMap);//API的ID用完了,它不是三方系統接口需要的請求參數,移除paramMap.remove("apiId"); //todo: 2.服務實例有效性驗證//request中去寫不同三方系統的鑒權、轉發或調用邏輯val responseData = request(headerMap, paramMap, apiDetailVo);//todo: 3.記錄消費記錄//返回第三方接口的響應結果return responseData;}/*** API轉發請求,對接時,針對不同的三方系統去定制化實現** @param headerMap 頭信息* @param paramMap  請求參數* @Param apiDetailVo 接口信息,如接口路徑、服務器host* @return 返回第三方接用調用的結果*/protected abstract Object request(Map<String, String> headerMap, Map<String, Object> paramMap, ApiDetailVo apiDetailVo);}

比如現在對接001號系統:按它們系統支持的方式做認證,比如header中添加APP密鑰,然后組裝請求URL成一個HttpRequest對象,發送Http請求即可完成對三方系統API的調用。

public class System001RedirectHandler extends AbstractRedirectHandler {@Overridepublic Object request(Map<String, String> headerParam, Map<String, Object> paramMap, ApiDetailVo apiDetailVo) {//拿到三方系統的服務器HOST以及接口路徑String url = apiDetailVo.getHost() + apiDetailVo.getPath();//拿到三方系統接口的請求方式,POST還是GET...val method = Method.valueOf(apiDetailVo.getRequestMethod());//使用Hutool工具類的HTTP請求對象,方便后面調用現成的方法來發送HTTP請求HttpRequest request = null;//如果是GETif (method.equals(Method.GET)) {String headerBody = "";StringBuffer body = new StringBuffer();StringBuffer param = new StringBuffer();for (String key : paramMap.keySet()) {body.append(key).append("=").append(paramMap.get(key)).append("&");param.append(key).append("=").append(URLEncoder.encode((String) paramMap.get(key), StandardCharsets.UTF_8)).append("&");}//拼接出一個GET請求的完整路徑if (param.length() > 0) {headerBody = body.substring(0, body.length() - 1);url = url + "?" + param.substring(0, param.length() - 1);}//創建request請求對象request = HttpUtil.createGet(url);//請求頭中加入001系統的認證秘鑰,以便通過認證request.addHeaders(getHeader(headerBody, apiDetailVo.getAppSecret()));} else {//POST請求request = HttpUtil.createPost(url).contentType("application/json");String body = JSON.toJSONString(paramMap);//組裝請求頭和請求體request.addHeaders(getHeader(body, apiDetailVo.getAppSecret())).body(body);}//庫里存的API有要求超時時間if (apiDetailVo.getTimeout() > 0) {request.timeout(apiDetailVo.getTimeout());}//發送HTTP請求,拿到響應val httpResponse = request.execute();return JSON.parseObject(httpResponse.body());}

3、疑問

給所有三方系統接口的調用一個統一的請求入口,怎么實現根據傳入的第三方系統類型platformType,來選擇不同的實現類對象:考慮把轉發接口ApiRedirectHandler的所有實現類放進一個List,遍歷去匹配傳入的platformType,匹配,則找到了三方系統對應的處理器實現類。找不到,就給個默認的處理器實現類。

@RequiredArgsConstructor
public class CompositeRedirectHandler {private ArrayList<ApiRedirectHandler> handlers = new ArrayList<>();public CompositeRedirectHandler(ArrayList<ApiRedirectHandler> redirectHandlerList) {handlers = redirectHandlerList;}public Object redirect(String platform, Map<String, String> headerMap, Map<String, Object> paramMap) {//給一個默認的通用執行器實現類對象ApiRedirectHandler execHandler = handlers.get(0);//根據平臺信息匹配到ApiRedirectHandler接口的三方系統的實現類for (ApiRedirectHandler handler : handlers) {if (handler.isMatched(platform)) {execHandler = handler;break;}}//用實現類去調用轉發方法  ==> 抽象類(包含抽象方法request) ==> 各個三方系統對抽象類的實現 ==> 完成三方系統API的請求return execHandler.redirect(headerMap, paramMap);}}

上面的接口中注入這個CompositeRedirectHandler對象,調用它的redirect方法,即可全部串起來。

private final CompositeRedirectHandler redirectHandler;@PostMapping("/data/{platform}/{apiId}")
public Object redirect(@PathVariable String platform, @PathVariable String apiId,@RequestBody Map<String, Object> parameterMap, HttpServletRequest request) {parameterMap.put("apiId", apiId);return redirectHandler.redirect(platform, ServletUtils.getHeaders(request), parameterMap);
}

4、存在的問題

如此,以后每對接一個三方系統,就得開發一個新的實現類,去按照他們系統支持的認證方式來做認證以及轉發或調用。相當繁瑣,現在考慮把這個認證的事交給三方系統自己去完成,比如讓他們開發一個SDK,SDK里他們按照自己系統支持的認證方式,做能通過鑒權的操作(到底是header里放密鑰還是做驗簽,我就不再關心了),以及組裝HTTP請求,而我們只需要load這個SDK里的內容,傳入請求參數和路徑,做一個調用即可。


@Slf4j
public class CommonRedirectHandler extends AbstractRedirectHandler {@Overridepublic Object request(Map<String, String> headerMap, Map<String, Object> paramMap, ApiDetailVo apiDetailVo) {//根據apiDetailInfo加載對應的SDK,完成調用//....}
}

如此,我就只需要一個通用的實現類CommonRedirectHandler就可以實現對所有三方系統的對接,這個通用類中也實現了上面的抽象類的request方法,request方法中只需load SDK里三方系統開發者寫的方法,傳入請求路徑和請求參數即可完成三方系統接口的調用。

5、通過反射加載SDK并完成調用

現在問題成了如何加載SDK,完成調用。 ? 通過反射拿到核心類的對象,以及負責認證和轉發請求的核心方法,最后完成調用即可。這里的反射直接用hutool這個強大的第三方依賴庫。

<!--引入hutool-->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.3.9</version>
</dependency>

加載SDK的示意代碼:

@Slf4j
public class CommonRedirectHandler extends AbstractRedirectHandler {/*** 動態加載sdk,調用里面已經完成鑒權和轉發的方法,以實現轉發請求** @param headerMap 頭信息* @param paramMap  請求參數* @return 三方系統接口的返回數據*/@Overridepublic Object request(Map<String, String> headerMap, Map<String, Object> paramMap, ApiDetailVo apiDetailVo) {//SDK的路徑、類名、核心方法名val jarFilePath = apiDetailVo.getSdkJarFilePath();   val classFullName = apiDetailVo.getClassFullName();val invokeMethodName = apiDetailVo.getInvokeMethodName();val httpMethod = apiDetailVo.getRequestMethod().toUpperCase();//拼接完整的三方系統接口的URLString apiUrl = apiDetailVo.getHost() + apiDetailVo.getPath();log.info("file = {}", new File(jarFilePath));//hutool工具類加載SDK成class對象Class<?> clazz = ClassLoaderUtil.loadClass(new File(jarFilePath), classFullName);//反射拿到構造方法對象final val constructors = ReflectUtil.getConstructor(clazz);Object instance = null;try {//SDK核心類的對象instance = constructors.newInstance();final val requestMethod = ReflectUtil.getMethodByName(clazz, invokeMethodName);//調用return requestMethod.invoke(instance, apiUrl, httpMethod, headerMap, paramMap);} catch (InstantiationException e) {log.error(e.getMessage());throw new ServiceException(ExceptionCodeEnum.API_GATEWAY_REQUEST_API_ERROR);} catch (IllegalAccessException e) {log.error(e.getMessage());throw new ServiceException(ExceptionCodeEnum.API_GATEWAY_REQUEST_API_ERROR);} catch (InvocationTargetException e) {log.error(e.getCause().getMessage());throw new ServiceException(ExceptionCodeEnum.API_GATEWAY_REQUEST_API_ERROR);}}
}

5、補充:關于業務網關

本需求里,給請求第三方系統接口資源提供了一個統一的API入口,比如:

@POSTMapping(/data/{platformType}/{API_ID})
public Object redirect(@PathVariable String platformType, @PathVariable String API_ID, @RequestBody Map<String, Object> requestParam, HttpServletRequset requset){//.....
}

有了這個統一入口,請求三方系統資源就都從這個接口過,前面說的各種合法性、有效性校驗、記錄落庫、轉發等就可以在這里完成了,由此可見,其雖然不比常規的Gateway服務,比如SpringCloudGateway,但干的活兒是類似的,即校驗和轉發(路由),因此,稱業務網關。

思路:給所有三方系統的api調用提供一個統一的入口(Api)

7、補充:關于SDK的開發

SDK,Software Development Kit,即軟件開發工具包。簡單說就是造輪子,實現一個小功能,別人引入,就能使用。往大了說,如Java開發工具包JDK,使用import引入相關的包:

import java.util.*;

往小了說,如文件上傳的SDK,其他系統引入后就可用。關于SDK的開發,需要注意:

  • 易用性:提供統一調用,用戶不用關心內部實現的細節,只需知道調誰、傳什么、能返回什么即可
//常見方式1.直接調用
FileManage.upload(String filePath);//常見方式2.需要new對象
FileManage fileManage = new FileManage();
fileManage.upload(String filePath);
  • 輕量依賴:盡量減少SDK本身對其他類庫的依賴,以減少用戶項目中的已有依賴和SDK依賴的沖突
  • 結構清晰:如maven項目下,service包下編寫業務邏輯、constant包下存放常量、utils包下放工具類
  • 見名知意:不用看說明文檔也知道這個方法是干啥用的
  • 可擴展:提供接口或者抽象類對外,支持用戶自己繼承和按需寫實現類,如密碼相關SDK,做加密解密,起名PasswordHandler,其加密方法encode需要傳入一個密碼,一個加密器,這個加密器就可以提供成接口,用戶可通過實現這個接口來自定義加密方式。
//加密器對象:按照非對稱算法加密
Encoder encoder = new SignEncoder();
String password = PasswordHandler.encode("daihao9527", encoder);
//用戶自己實現加密器接口
public MyEncoder implements Encoder {@Overridevoid doEncode(){//...用戶自己寫,如采用時間戳、或自定義的MD5工具類Calendar calendar = Calendar.getInstance();Long timestamp = calendar.getTime.getTime();String sign = MD5Utils.getMD5Str(timestamp + secretKey);//..}
}
//此時,用戶可以自己指定加密器
String password = PasswordHandler.encode("daihao9527", new MyEncoder());

SDK中的內容一般包括:

  • 功能模塊:實現功能
  • API:SDK的門面,調用和使用功能的入口
  • 文檔:附相關使用說明和指引
  • Demo:使用示例,運行Demo,直觀體驗SDK功能

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/212185.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/212185.shtml
英文地址,請注明出處:http://en.pswp.cn/news/212185.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JAVA如何調用python

以下代碼想通過測試&#xff0c;必須有一個前提&#xff1a;電腦上安裝了Python環境。不太習慣說廢話&#xff0c;直接上代碼了。 以下是用于測試的python代碼&#xff08;mytest.py&#xff09;&#xff1a; # 因為用戶到了參數處理&#xff0c;所以需要引用 import argpars…

Java學習手冊——第五篇數據類型

數據類型&#xff1a;是數據化的基石&#xff0c;如果沒有數據類型怎么表示呢&#xff1f;比如年齡可以用整數&#xff1a;18歲。如果有更好的表示方式大家可以留言喲~ 在舉個例子就是姓名&#xff0c;我們需要用字符串的形式來表示。這就是數據類型的魅力&#xff0c;而又有同…

TS基礎語法

前言&#xff1a; 因為在寫前端的時候&#xff0c;發現很多UI組件的語法都已經開始使用TS語法&#xff0c;不學習TS根本看不到懂&#xff0c;所以簡單的學一下TS語法。為了看UI組件的簡單代碼&#xff0c;不至于一臉懵。 一、安裝node 對于windows來講&#xff0c;node版本高…

電腦出現這些現象,說明你的固態硬盤要壞了

與傳統機械硬盤&#xff08;HDD&#xff09;相比&#xff0c;固態硬盤&#xff08;SSD&#xff09;速度更快、更穩定、功耗更低。但固態硬盤并不是完美無瑕的&#xff0c;由于顆粒寫入機制&#xff0c;可能會在七到十年的預期壽命之前出現故障。所以用戶最好為最終故障做好準備…

網頁設計中增強現實的興起

目錄 了解增強現實 增強現實的歷史背景 AR 和網頁設計的交叉點 AR 在網頁設計中的優勢 增強參與度和互動性 個性化的用戶體驗 競爭優勢和品牌差異化 AR 在網頁設計中的用例 結論 近年來&#xff0c;增強現實已成為一股變革力量&#xff0c;重塑了我們與數字領域互動的方式。它被…

【FMCW毫米波雷達設計 】 — FMCW波形

原書&#xff1a;FMCW Radar Design 1 引言 本章研究驅動FMCW雷達的主要波形:線性調頻(LFM)波形。我們研究信號的行為及其性質。隨后&#xff0c;本章討論了匹配濾波理論&#xff0c;并研究了壓縮這種波形的技術&#xff0c;特別是所謂的拉伸處理&#xff0c;它賦予FMCW雷達極…

DOS 批處理 (二)

DOS 批處理 1. 基礎 DOS 命令1.1 基礎命令1.2 文件系統操作1.3 文件夾管理1.4 文件管理1.5 網絡相關1.6 系統管理1.7 IF、FOR和NETIFFORNET 1. 基礎 DOS 命令 command /? 查找幫助DOS命令不區分命令字母的大小寫 C:\Users\Administrator>echo 1 1 C:\Users\Administrator…

基于SSM框架的倉庫管理系統

基于SSM框架的倉庫管理系統 文章目錄 基于SSM框架的倉庫管理系統 一.引言二.系統設計三.技術架構四.功能實現五.界面展示六.源碼獲取 一.引言 現代商業環境中&#xff0c;倉庫管理對于企業的運營效率和客戶滿意度至關重要。傳統的手工管理方式已經無法滿足日益復雜的倉儲需求。…

【Spring】SpringBoot日志

SpringBoot日志 日志概述日志使用打印日志獲取日志對象使用日志對象打印日志日志框架介紹門面模式SLF4J框架介紹(simple logging facade for java) 日志格式說明日志級別日志級別的分類日志級別的使用 日志配置配置日志級別日志持久化配置日志文件的路徑和文件名配置日志文件的…

【刷題篇】動態規劃(六)

文章目錄 1、最大子數組和2、環形子數組的最大和3、乘積最大子數組4、乘積為正數的最長子數組長度5、 等差數列劃分6、最長湍流子數組 1、最大子數組和 給你一個整數數組 nums &#xff0c;請你找出一個具有最大和的連續子數組&#xff08;子數組最少包含一個元素&#xff09;&…

【Unity動畫】Avatar Mask

創建 Avatar Mask可以設置那一部分骨骼運動和不運動 然后放在狀態機里面的層中來混合 【后續完善】

深入探索 Rust 宏編程

Rust 宏提供了一種強大的方法來編寫抽象和重用代碼,它們在 Rust 編程中扮演著重要的角色。本文將深入探索 Rust 宏的概念、類型、使用方法以及如何實現自定義宏,以提供一個全面的 Rust 宏編程指南。 Rust 宏簡介 宏是 Rust 中的一種元編程工具,它們在編譯時運行,用于生成…

linux安裝node

文章目錄 安裝node 安裝node 一次手操記錄 - 首先安裝wget yum install -y wget - 下載nodejs最新的tar包 wget https://cdn.npm.taobao.org/dist/node/v12.12.0/node-v12.12.0-linux-x64.tar.xz - 解壓包 tar -xvf node-v12.12.0-linux-x64.tar.xz - 部署bin文件 先確認你no…

30 張圖解 HTTP 常見的面試題

前言 在面試過程中&#xff0c;HTTP 被提問的概率還是比較高的 我搜集了 5 大類 HTTP 面試常問的題目&#xff0c;同時這 5 大類題跟 HTTP 的發展和演變關聯性是比較大的&#xff0c;通過問答 圖解的形式由淺入深的方式幫助大家進一步的學習和理解 HTTP 協議。 HTTP 基本概…

第四節JavaScript 條件語句、循環語句、break與continue語句

一、JavaScript條件語句 在通常的代碼中&#xff0c;我們有一些需要決定執行不同動作&#xff0c;這就可以在代碼中使用條件語句來完成。 下面是我們常使用的條件語句&#xff1a; if語句&#xff1a;只有當指定條件是true時&#xff0c;執行條件內代碼。if…else語句&#…

JavaScript數組的長度

JavaScript數組的長度可以通過數組對象的length屬性來獲取&#xff0c;長度表示數組中元素的數量。 代碼示例&#xff1a; let arr []; // 定義一個空數組 console.log(arr.length); // 輸出 0arr.push(1); // 給數組添加元素 arr.push(2); arr.push(3); console.log(arr.le…

項目二 創建與操作學生管理數據庫

項目二 創建與操作學生管理數據庫 #目標 創建庫&#xff1b;查看庫&#xff1b;操作庫&#xff1b;圖形工具操作庫1&#xff0c;創建學生管理數據庫 #創建數據庫 CREATE DATABASE [IF NOT EXISTS] db_name [[DEFAULT] CHARACTER SET charset_name] [[DEFAULT] COLLATE collat…

44.0/認識前端

44.1 目錄 44.1.1 網頁 44.1.1.1 網頁的組成 44.1.1.2 網頁的分類 44.1.2 網站 44.1.2.1 網站的分類 44.1.3 主頁 44.2. Internet、IP 地址和域名 44.2.1 Internet 44.2.2 IP 44.2.3 域名 44.3. Web 前端技術概述 44.3.1 html5 44.3.2 CSS3 44.3.3 Javascript …

hbuiler中使用npm安裝datav

注&#xff1a;datav邊框樣式目前使用時&#xff1a;適用于網頁&#xff0c;不適用于app 1、先安裝node 安裝、配置Node路徑 2、為Node配置環境變量 3、在hbuilder的設置中填寫node的路徑 配置 4、打開cmd輸入npm install jiaminghi/data-view 安裝dataV&#xff0c;&…

當初為什么選擇計算機-希望一直干下去

還記得當初自己為什么選擇計算機&#xff1f; 當初你問我為什么選擇計算機&#xff0c;我笑著回答&#xff1a;“因為我夢想成為神奇的碼農&#xff01;我想像編織魔法一樣編寫程序&#xff0c;創造出炫酷的虛擬世界&#xff01;”誰知道&#xff0c;我剛入門的那天&#xff0…