博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于Dubbo的http自动测试工具分享
阅读量:6118 次
发布时间:2019-06-21

本文共 7156 字,大约阅读时间需要 23 分钟。

公司是采用微服务来做模块化的,各个模块之间采用dubbo通信。好处就不用提了,省略了之前模块间复杂的http访问。不过也遇到一些问题:

PS:

测试需要配合写消费者的代码

对于开发来说,倒是挺省劲。但是对于测试来说就有点麻烦了, 每次还要去写dubbo的消费程序,而且每次新增一个接口,都需要重新改写程序,费时费力。

接口返回的结果无法定制

由于我这边是做一些商品的推荐,每次结果的类型都是相同的,只是内部的算法不同。不过接口只是返回id,无法直观的判断商品相似程度或者用户的偏好程度,需要一个可视化的返回结果界面。

于是在这种需求下,我设想了一个小程序,它可以满足下面的功能:

  1. 测试可以根据测试需要,在界面自动选择请求的class和方法
  2. 开发完成后,测试界面自动扫描出dubbo的提供者的class和对应的方法
  3. 返回结果自动请求对应的图片和文字说明

提前放一个效果图:

449064-20180105173302487-1559723516.jpg

1 扫描某个包下所有的类

小程序开始的第一步就是需要扫描某个包下所有的dubbo实现类。

由于工程是springboot,因此最终部署是在jar中。这时,就需要面临两个问题,如果是在开发工具中,如何获取包下的所有类;如果是在jar中,如何获取包下所有的类。

首先通过classloader可以加载特定路径下的所有URL:

Enumeration
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);while (dirs.hasMoreElements()) { URL url = dirs.nextElement(); //如果是jar,则采用JarURLConnection的方式连接,获得class文件 if("jar".equals(url.getProtocol())){ findClassesInJar(url,classes,pack); }else{ findClassesInSrc(url,classes,pack); }}

在工程中

在工程中,class其实是以目录形式存放在本地的,直接按照file的方式遍历扫描class文件就行了:

public static void findClassesInSrc(URL url, Set
> classes, String basePackage) throws UnsupportedEncodingException { File dir = new File(URLDecoder.decode(url.getFile(), "UTF-8")); if (!dir.exists() || !dir.isDirectory()) { return; } Arrays.stream(dir.listFiles()) .forEach(file -> { String className = file.getName().substring(0, file.getName().length() - 6); try { classes.add(Thread.currentThread().getContextClassLoader().loadClass(basePackage + '.' + className)); } catch (ClassNotFoundException e) { e.printStackTrace(); } });}

在jar包中

jar包是一种特殊的压缩包,java提供了JarFile类的entries方法,可以遍历jar中所有的文件。不过这里就没有目录或者文件的区别了,因为都是一个zip包中的资源而已。因此最后需要针对报名进行一下过滤:

public static void findClassesInJar(URL url,Set
> classes,String basePackage) throws IOException, ClassNotFoundException { //转换为JarURLConnection JarURLConnection connection = (JarURLConnection) url.openConnection(); if (connection != null) { JarFile jarFile = connection.getJarFile(); if (jarFile != null) { //得到该jar文件下面的类实体 Enumeration
jarEntryEnumeration = jarFile.entries(); while (jarEntryEnumeration.hasMoreElements()) { JarEntry entry = jarEntryEnumeration.nextElement(); String jarEntryName = entry.getName(); //这里我们需要过滤不是class文件和不在basePack包名下的类 if (jarEntryName.contains(".class") && jarEntryName.replaceAll("/",".").startsWith(basePackage)) { String className = jarEntryName .substring(0, jarEntryName.lastIndexOf(".")) .replace("/", "."); classes.add(Thread.currentThread().getContextClassLoader().loadClass(className)); } } } }}

2 扫描某个class下所有的方法

获得某个类的所有方法

然后通过反射可以直接通过class的名字,拿到它的所有方法,这些方法里面包含了一些通用的方法,如wait,notify等,需要给过滤掉。

public static Set
NORMAL_METHODS = new HashSet<>(Arrays.asList("wait","equals","toString","hashCode","getClass","notify","notifyAll"));public static List
getMethod(String className){ try { Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className); Method[] methods = clazz.getMethods(); return Arrays.stream(methods) .filter(method -> !NORMAL_METHODS.contains(method.getName())) .collect(Collectors.toList()); } catch (ClassNotFoundException e) { e.printStackTrace(); } return new ArrayList<>();}

这里需要注意,两个不同参数的方法,虽然名字相同,但是他们的parameterTypes是不同的。因此这里最好直接返回method,把name和parameterTypes一同作为结果返回。因为最终invoke的时候,还得通过参数类型把所有的参数都转换类型一下。

3 方法的执行

第三个难点,就是前端传过来的参数都是字符串,比如:

  • com.xingoo.test.Provider1Impl 是对应的class
  • test1 是对应的方法
  • 100 是对应的参数
  • java.lang.Long 是参数对应的类型

怎么能把请求通过正确的dubbo provider执行呢?——答案 就是Bean

因为在Spring的项目中,dubbo的provider都是一个单例的bean。因此可以直接通过applicationContext获得对应的bean,只要保证bean的名字能规律的映射过来就行。

可以参考下面的获取bean的方法:

import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;@Componentpublic class SpringUtils implements ApplicationContextAware {    private static ApplicationContext applicationContext = null;// 非@import显式注入,@Component是必须的,且该类必须与main同包或子包    // 若非同包或子包,则需手动import 注入,有没有@Component都一样    // 可复制到Test同包测试    @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {        if(SpringUtils.applicationContext == null){            SpringUtils.applicationContext  = applicationContext;        }    }    //获取applicationContext    public static ApplicationContext getApplicationContext() {        return applicationContext;    }    //通过name获取 Bean.    public static Object getBean(String name){        return getApplicationContext().getBean(name);    }    //通过class获取Bean.    public static 
T getBean(Class
clazz){ return getApplicationContext().getBean(clazz); } //通过name,以及Clazz返回指定的Bean public static
T getBean(String name,Class
clazz){ return getApplicationContext().getBean(name, clazz); }}

在真正的实现类上,需要指定bean的名字:

@Service("Provider1Impl")public class Provider1Impl implements ProviderApi {...}

然后利用反射,就可以执行这个bean的特定方法了:

// 反射拿到对应的classClass cla = Thread.currentThread().getContextClassLoader().loadClass(clazz);// 在appContext中拿到对应的beanObject bean = SpringUtils.getBean(cla.getSimpleName());// 格式化参数与参数类型Class
[] parameterTypes = DubboApiUtils.paramTypeFormat(types);Object[] parameters = DubboApiUtils.paramsFormat(params,types);// 通过反射调用对应的方法return cla.getMethod(method, parameterTypes).invoke(bean,parameters);

对应参数处理的两个方法是:

/**     * 根据字符串拼接,获得对应的参数类型数组     * @param types     * @return     */    public static Class
[] paramTypeFormat(String types){ List
> paramsClasses = new ArrayList<>(); for(String type : types.split(",")){ try { paramsClasses.add(Class.forName(type)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return paramsClasses.toArray(new Class[]{}); } /** * 根据参数类型,转换类型 * @param paramStr * @param types * @return */ public static Object[] paramsFormat(String paramStr,String types){ Class
[] classes = paramTypeFormat(types); List
formats = new ArrayList<>(); String[] params = paramStr.split(","); for(int i =0;i

4 商品自动请求描述信息

最后就是jquery基于ajax请求,查询对应的接口结果就行了。需要注意ajax的同步问题:

$.ajax({    type : "post",    url : "xxxxx",    data : {xxx:xxx},    async : false,    success : function(r){                    //todo            }});

总结

总结来说,下面是遇到的问题和简单的对应办法:

  • 1 如何扫描工程或者普通web项目 某个包下的class——通过classloader获得路径,直接遍历file即可
  • 2 如何扫描jar中某个包下的class——通过JarFile获得对应的JarEntry
  • 3 如何获取Spring Boot中的Bean——通过实现ApplicationContextAware接口,获取applicationContext的引用
  • 4 如何动态执行某个对象的特定方法——基于反射method.invoke,需要注意传入的参数与类型问题

通过这样一个小工具,又对反射有了更进一步的了解。:-)))))))))

参考:

转载地址:http://prlka.baihongyu.com/

你可能感兴趣的文章
淘宝的几个架构图
查看>>
Android扩展 - 拍照篇(Camera)
查看>>
JAVA数组的定义及用法
查看>>
充分利用HTML标签元素 – 简单的xtyle前端框架
查看>>
设计模式(十一):FACADE外观模式 -- 结构型模式
查看>>
iOS xcodebuile 自动编译打包ipa
查看>>
程序员眼中的 SQL Server-执行计划教会我如何创建索引?
查看>>
【BZOJ】1624: [Usaco2008 Open] Clear And Present Danger 寻宝之路(floyd)
查看>>
cmake总结
查看>>
数据加密插件
查看>>
linux后台运行程序
查看>>
win7 vs2012/2013 编译boost 1.55
查看>>
IIS7如何显示详细错误信息
查看>>
ViewPager切换动画PageTransformer使用
查看>>
coco2d-x 基于视口的地图设计
查看>>
C++文件读写详解(ofstream,ifstream,fstream)
查看>>
Android打包常见错误之Export aborted because fatal lint errors were found
查看>>
Tar打包、压缩与解压缩到指定目录的方法
查看>>
新手如何学习 jQuery?
查看>>
配置spring上下文
查看>>