EasyMock技术解密

Tech
导读
本文主要介绍了EasyMock平台及JSF Mock实现技术,后续会继续编写一系列文章,分享更多Mock相关技术。EasyMock平台在2021年开展开源共建,并获得2021年行云1024研发效能共建最佳新锐奖,目前正在集团开源共建中,欢迎联系我们一起共建!
EasyMock平台面向集团产品、研发、测试人员,提供的一款完全模拟服务端Mock的平台,支持JSF、HTTP接口Mock服务,支持测试环境/线上环境多站点,灵活的接口出入参设置,可以方便返回想要的Mock数据。平台自2021年10月上线行云,面向集团进行推广,累计接入C1部门 49个(涉及零售、科技、物流、健康等BU),涵盖用户2000+,月均Mock调用量1000万+。
以上只是Mock平台的部分功能,平台还有更多内容值得探索。
接下来了解EasyMock提供的平台能力:
1.支持多协议Mock:JSF、HTTP;
2.支持测试/线上环境;
3.同接口多版本、多别名支持;
4.接口与方法分开控制,支持服务透传,调用真实服务;(平台亮点)
1)方法级别透传:被测应用调用同一接口的不同方法,可实现一个方法Mock,一个方法调用真实的服务;
2)参数模版级别:被Mock的方法匹配不到参数模版时,可设置调用真实的服务(即将上线);
5. 参数数据模板管理:支持参数正则匹配、出入参自动解析、自动生成、参数化、参数传递、异常模拟等;(平台亮点)
1)支持参数正则匹配:多种参数匹配方式,优先全量匹配、部分匹配、正则匹配、默认匹配;
2)出入参自动解析、自动生成:不知道出入参格式怎么办?平台支持参数解析、出参自动生成;
3)参数传递:想返回的出参取客户端调用传进来的入参值;
2.1 平台整体设计
如下图所示,平台整体采用主、从服务部署,主服务面向用户,提供服务管理、模版管理、应用管理(规划中)、看板等功能,从服务提供接口Mock服务,供客户端调用,主服务通过IP分配规则控制从服务进行接口Mock开启/关闭。
图1 平台全景图
2.2 JSF技术实现步骤
从技术角度来说下JSF Mock的整个流程,用户访问平台,添加要Mock的JSF接口和方法,主服务会异步下载接口所依赖的Jar包,用户开启Mock,主服务按分配规则通知从服务开启Mock,从服务将接口所依赖Java类加载到JVM,通过动态代理将接口实例化,同时将接口注册到JSF册中心,一个接口就Mock完成。这时客户端请求Mock服务,从服务接收到客户端请求,后台根据接口、方法匹配Mock接口,同时根据客户端请求的入参进行参数匹配,匹配到设置的参数,通过反序列化将出参返回。可以将整体流程概况为7个技术知识点,然后逐一讲解:
图2 JSF技术实现步骤
2.2.1 Jar包下载
用户在添加JSF接口时,需要指定pom坐标,后台程序根据pom坐标去下载所需要的Jar包,并存储在NFS服务器。实现流程如下:
1.指定pom文件,未指定则去maven私服获取最新上传的jar包;这里支持排除exclusions2.根据pom坐标,生成pom文件3.异步下载(@EnableAsync),执行mvn命令:mvn clean dependency:copy-dependencies,这地方会将该接口所依赖的Jar包都会进行下载;
新增接口页面:
下载Jar包后,需要通过ClassLoader将Jar包加载到JVM,这里采用URLClassLoader进行加载,URLClassLoader继承于ClassLoader,支持从Jar文件和文件夹中获取Class。首先获取系统的classLoader,遍历Jar包进行动态加载,最后通过loadClass加载接口类。
示例代码:
// 拿到系统的classLoaderURLClassLoader urlClassLoaderForJvm = (URLClassLoader) ClassLoader.getSystemClassLoader();Class<URLClassLoader> urlClass = URLClassLoader.class;Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});method.setAccessible(true);for (File file : files) {logger.info("动态加载jar包:{}", file.getAbsolutePath());URL url = new URL("file:" + file);method.invoke(urlClassLoaderForJvm, new Object[]{ url });}try {cls = urlClassLoaderForJvm.loadClass(interfaceName);} catch (NoClassDefFoundError e) {logger.error("不能正常解析类NoClassDefFoundError, name:" + interfaceName);} catch (Exception e) {logger.error(" 不能正常解析类Exception: " + e.toString());}
ClassLoader结构
图4 ClassLoader结构
2.2.3 类实例化
类实例化主要通过动态代理实现,Java动态代理位于java.lang.reflect包下,一般主要涉及到以下两个类:
1. InvocationHandler:该接口中仅定义了一个方法,每一个代理都要实现接口InvocationHandler,通过invoke进行调用方法。
Object invoke(Object proxy, Method method, Object[] args)throwsThrowable2. Proxy:该类即为动态代理类,这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是newProxyInstance 这个方法:
Public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler handdler)throwsIllegalArgumentException图5 动态代理示例
动态代理实现步骤:
1.创建一个实现接口InvocationHandler的类,并实现invoke方法;
2.创建被代理的类以及接口;
3.调用Proxy的静态方法,创建一个代理类Proxy.newProxyInstance(classLoader, interfaces, proxy);
4.通过代理调用方法。
代码示例:
/*** JDK动态代理代理类**/@Servicepublic class FacadeProxy implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Exception {// mock处理// mock参数匹配String response = matchParams(int methodId,Object[] args,Method method);// mock出参返回return new Gson().fromJson(response, method.getReturnType());}public static <T> T newMapperProxy(Class<T> mapperInterface) throws Throwable{ClassLoader classLoader = mapperInterface.getClassLoader();Class<?>[] interfaces = new Class[] { mapperInterface };FacadeProxy proxy = new FacadeProxy();T t = (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);return t;}}
2.2.4 JSF接口注册/注销
采用JSF API的方式进行接口注册/注销。目前的API方式和Spring方式里的属性都是一一对应的,spring的方式无非就是spring转换为api的方式进行发布。
这里参考JSF API即可:https://cf.jd.com/pages/viewpage.action?pageId=296129902
2.2.5 客户端调用
Mock接口注册到JSF注册中心,客户端调用mock别名(Alias)即可。
2.2.6 参数匹配
参数匹配这里会依顺序进行以下四种方式匹配,匹配到就直接返回。
1. 优先对象匹配:参数截取->参数转对象->对象比较;
2. 字符串完成匹配、部分匹配;
3. 正则匹配:Java正则匹配;
4. 默认匹配:.*或*。
2.2.7 参数返回
匹配到数据模版后,将匹配到的出参转换成客户端想要的类型则需要将出参进行反序列化,转换为mock接口对应的出参类型返回。
反序列化是本文的一个难点,出参类型格式各样,通过进行各种尝试,不敢说所有,至少当前接入的接口都已支持。
参数类型主要有以下几种:基本类型、字符串、简单对象、复杂对象、泛型;对于基本类型、字符串,转换为对应类型直接返回即可;对于简单对象,通过fastjson转换即可;对于泛型、复杂对象,会尝试fastjson、gson、指定class 3种方式进行转换。
代码示例:
// 1.获取Mock接口出参类型:Type genericReturnType = method.getGenericReturnType();// 2.基本类型转换:object = Integer.valueOf(response);// 3.优先fastjson转换返回:object = JSON.parseObject(resultString,genericReturnType);// 4.fastjson转换对象失败,改为gson转换object = gson.fromJson(resultString,genericReturnType);// 5. 返回对象为Object,客户端解析时需要具体的类,这时需要在返回参数指定class,这个通过PojoUtils提供的realize方法转换object=JSON.parseObject(resultString,Map.class);
以上为JSF Mock的实现过程,后续会继续分享HTTP Mock的实现过程及平台开发过程中解决的各种技术难点。目前EasyMock正在开源共建中,也欢迎更多有想法的小伙伴一起共建,进行技术交流,打造集团高质量Mock产品。
求分享
求点赞
求在看