反射加载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.mzph.cn/news/212185.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电脑出现这些现象,说明你的固态硬盘要坏了

与传统机械硬盘&#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可以设置那一部分骨骼运动和不运动 然后放在状态机里面的层中来混合 【后续完善】

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语句&#…

项目二 创建与操作学生管理数据库

项目二 创建与操作学生管理数据库 #目标 创建库&#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…

.360勒索病毒数据恢复|金蝶、用友、管家婆、OA、速达、ERP等软件数据库恢复

尊敬的读者&#xff1a; 在数字时代&#xff0c;.360勒索病毒如同数字的幽灵&#xff0c;悄无声息地侵入用户的数字领域&#xff0c;将珍贵的数据文件变为数字的囚牢。本文将介绍.360勒索病毒的特征&#xff0c;提供解密和数据恢复的方法&#xff0c;并分享有效的预防措施&…

【开源】基于JAVA语言的数字化社区网格管理系统

项目编号&#xff1a; S 042 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S042&#xff0c;文末获取源码。} 项目编号&#xff1a;S042&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、开发背景四、系统展示五、核心源码5…

3.DevEco Studio安装鸿蒙手机app本地模拟器

配合Intel CPU启动模拟器 解决措施 打开任务管理器&#xff0c;在“性能”选项&#xff0c;检查CPU虚拟化是否已经启用。如果未启用&#xff0c;需要进入电脑的BIOS中&#xff0c;将CPU的“Intel Virtualization Technology”选项开启。 点击New Emulator 文档中心 解决措施…

铁路通信铁塔监测方案

目录 1.监测的背景及意义 1.1监测背景 1.2监测意义 2.系统介绍及特点 2.1系统介绍 2.2系统特点 3.系统设计 3.1监测内容 3.2总体介绍 3.3详细设计 3.3.1垂直度监测 3.3.2水平位移、沉降监测 3.3.3环境监测 3.3.4应力应变监测 3.3.5裂缝监测 3.3.6云平台综合在线…

VBA技术资料MF93:将多个Excel表插入PowerPoint不同位置

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到…

TypeScript 之 console的使用

语言&#xff1a; TypeScript 在线工具&#xff1a; PlayGround console console 对象是一个非常强大的控制台日志显示工具&#xff0c; 可以帮助我们在浏览器中调试代码。 注&#xff1a; console不属于TypeScript的语法&#xff0c;而是由JavaScript封装的内置对象。 简单的…