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

PS: Github的代码示例

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

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

接口返回的结果无法定制

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

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

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

提前放一个效果图:

1 扫描某个包下所有的类

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

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

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

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

在工程中

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

  1. public static void findClassesInSrc(URL url, Set<Class<?>> classes, String basePackage) throws UnsupportedEncodingException {
  2. File dir = new File(URLDecoder.decode(url.getFile(), "UTF-8"));
  3. if (!dir.exists() || !dir.isDirectory()) {
  4. return;
  5. }
  6. Arrays.stream(dir.listFiles())
  7. .forEach(file -> {
  8. String className = file.getName().substring(0, file.getName().length() - 6);
  9. try {
  10. classes.add(Thread.currentThread().getContextClassLoader().loadClass(basePackage + '.' + className));
  11. } catch (ClassNotFoundException e) {
  12. e.printStackTrace();
  13. }
  14. });
  15. }

在jar包中

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

  1. public static void findClassesInJar(URL url,Set<Class<?>> classes,String basePackage) throws IOException, ClassNotFoundException {
  2. //转换为JarURLConnection
  3. JarURLConnection connection = (JarURLConnection) url.openConnection();
  4. if (connection != null) {
  5. JarFile jarFile = connection.getJarFile();
  6. if (jarFile != null) {
  7. //得到该jar文件下面的类实体
  8. Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
  9. while (jarEntryEnumeration.hasMoreElements()) {
  10. JarEntry entry = jarEntryEnumeration.nextElement();
  11. String jarEntryName = entry.getName();
  12. //这里我们需要过滤不是class文件和不在basePack包名下的类
  13. if (jarEntryName.contains(".class") && jarEntryName.replaceAll("/",".").startsWith(basePackage)) {
  14. String className = jarEntryName
  15. .substring(0, jarEntryName.lastIndexOf("."))
  16. .replace("/", ".");
  17. classes.add(Thread.currentThread().getContextClassLoader().loadClass(className));
  18. }
  19. }
  20. }
  21. }
  22. }

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

获得某个类的所有方法

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

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

这里需要注意,两个不同参数的方法,虽然名字相同,但是他们的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的方法:

  1. import org.springframework.beans.BeansException;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.ApplicationContextAware;
  4. import org.springframework.stereotype.Component;
  5. @Component
  6. public class SpringUtils implements ApplicationContextAware {
  7. private static ApplicationContext applicationContext = null;
  8. // 非@import显式注入,@Component是必须的,且该类必须与main同包或子包
  9. // 若非同包或子包,则需手动import 注入,有没有@Component都一样
  10. // 可复制到Test同包测试
  11. @Override
  12. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  13. if(SpringUtils.applicationContext == null){
  14. SpringUtils.applicationContext = applicationContext;
  15. }
  16. }
  17. //获取applicationContext
  18. public static ApplicationContext getApplicationContext() {
  19. return applicationContext;
  20. }
  21. //通过name获取 Bean.
  22. public static Object getBean(String name){
  23. return getApplicationContext().getBean(name);
  24. }
  25. //通过class获取Bean.
  26. public static <T> T getBean(Class<T> clazz){
  27. return getApplicationContext().getBean(clazz);
  28. }
  29. //通过name,以及Clazz返回指定的Bean
  30. public static <T> T getBean(String name,Class<T> clazz){
  31. return getApplicationContext().getBean(name, clazz);
  32. }
  33. }

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

  1. @Service("Provider1Impl")
  2. public class Provider1Impl implements ProviderApi {
  3. ...
  4. }

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

  1. // 反射拿到对应的class
  2. Class cla = Thread.currentThread().getContextClassLoader().loadClass(clazz);
  3. // 在appContext中拿到对应的bean
  4. Object bean = SpringUtils.getBean(cla.getSimpleName());
  5. // 格式化参数与参数类型
  6. Class<?>[] parameterTypes = DubboApiUtils.paramTypeFormat(types);
  7. Object[] parameters = DubboApiUtils.paramsFormat(params,types);
  8. // 通过反射调用对应的方法
  9. return cla.getMethod(method, parameterTypes).invoke(bean,parameters);

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

  1. /**
  2. * 根据字符串拼接,获得对应的参数类型数组
  3. * @param types
  4. * @return
  5. */
  6. public static Class<?>[] paramTypeFormat(String types){
  7. List<Class<?>> paramsClasses = new ArrayList<>();
  8. for(String type : types.split(",")){
  9. try {
  10. paramsClasses.add(Class.forName(type));
  11. } catch (ClassNotFoundException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. return paramsClasses.toArray(new Class[]{});
  16. }
  17. /**
  18. * 根据参数类型,转换类型
  19. * @param paramStr
  20. * @param types
  21. * @return
  22. */
  23. public static Object[] paramsFormat(String paramStr,String types){
  24. Class<?>[] classes = paramTypeFormat(types);
  25. List<Object> formats = new ArrayList<>();
  26. String[] params = paramStr.split(",");
  27. for(int i =0;i<classes.length; i++){
  28. //todo 简单粗暴,有其他的需要再加吧
  29. if("Long".equals(classes[i].getSimpleName())){
  30. formats.add(Long.valueOf(params[i]));
  31. }else{
  32. formats.add(params[i]);
  33. }
  34. }
  35. return formats.toArray();
  36. }

4 商品自动请求描述信息

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

  1. $.ajax({
  2. type : "post",
  3. url : "xxxxx",
  4. data : {xxx:xxx},
  5. async : false,
  6. success : function(r){
  7. //todo
  8. }
  9. });

总结

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

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

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

基于Dubbo的http自动测试工具分享的更多相关文章

  1. 基于数据库的代码自动生成工具,生成JavaBean、生成数据库文档、生成前后端代码等(v6.0.0版)

    TableGo v6.0.0 版震撼发布,此次版本更新如下: 1.UI界面大改版,组件大调整,提升界面功能的可扩展性. 2.新增BeautyEye主题,界面更加清新美观,也可以通过配置切换到原生Jav ...

  2. Qtp自动测试工具

    QTP是基于GUI界面的自动化测试工具,用于系统的功能测试. QTP录制的是鼠标和键盘的消息.QTP录制回放时基于windows操作系统的消息机制.QTP在录制时监听应用程序的消息,监听到之后把消息放 ...

  3. 自动测试工具(Jmeter,qtp等)

     loadrunner.Selenium.QTP三者区别?    Loadrunner是商业性能测试工具,收费,功能强大,适合做复杂场景的性能测试.  Selenium是开源的web自动测试工具,免费 ...

  4. Android WebDriver 浏览器自动测试工具介绍

    Selenium WebDriver 是浏览器自动测试工具,提供轻量级和优雅的方式来测试web应用.Selenium WebDriver作为Android SDK extra,支持Android 2. ...

  5. Android蓝牙连接自动测试工具

    蓝牙连接自动测试工具 1.需求产生 开发不按着需求走都是耍流氓且浪费时间.此工具的需求产生是研发人员在开发产品时涉及到蓝牙驱动和安卓蓝牙两个东西.但是呢,蓝牙不太稳定,那么工作来了.就需要研发人员一边 ...

  6. 自动测试工具SilkTest全面介绍

    象交互,并最终记录测试结果,用户可以根据这些测试结果来判断测试成功还是失败. 4Test 脚本语言 和绝大多数自动化测试工具一样, SilkTest 可以自动捕捉,检测和重复用户交互的操作从而驱动测试 ...

  7. 基于Java的四大开源测试工具

    摘要:成功的应用程序离不开测试人员和QA团队反复地测试,应用程序在进行最后的部署之前,需要通过测试来确保它的负载管理能力以及在特殊情况下的工作条件和工作加载情况. %R[)vA t]N0 测试是应用程 ...

  8. 开源you-get项目爬虫,以及基于python+selenium的自动测试利器

    写在前面 爬虫和自动测试,对于python来说是最合适不过也是最擅长的. 开源的项目也很多,例如you-get项目https://github.com/soimort/you-get.盗链和爬虫神器. ...

  9. Py福利,基于uiautomatorviewer 的Python 自动化代码自动生成工具分享(jar已发布GitHub,欢迎Star)

    前言做UI自动化无论你用SDK自带的uiautomatorviewer还是Macaca还是Appium自动的inspector,代码最多的就是那些繁琐重复的找元素后点击,输入,长按.....等.现在偷 ...

随机推荐

  1. Windows Intellij环境下Gradle的 “Could not determine Java version from ‘9.0.1’”的解决方式

    当我导入Gradle项目初试Java spring的时候,遇到下面报错: Gradle complete project refresh failed Error:Could not determin ...

  2. php加密字符串超时不可解密

    <?php/** * 加密字符串在指定时间内解密有效 * @param  [type]  $string    明文字符串 * @param  string  $operation 解密值为DE ...

  3. YII2连表分页

    控制器(controller)页面 use \yii\data\Pagination; //引入这个类 public function actionList(){ $data = Clock::fin ...

  4. Require,js配置使用心得

    首先大家要知道requirejs是干嘛用的,要解释,那就用一句话说下:RequireJS是一个JavaScript文件和模块加载器接下来我们开始学会配置使用requireJs,当然在学习使用的过程中也 ...

  5. 一道题Wrong Answer之后该何去何从?

    写程序手不稳是个大毛病,往往会让一份能AC的代码变成99.995%正确,失之毫厘谬以千里,近期十场个人赛非常少有能一次AC的经历,细致想想除了根本逻辑上的错误.大概都是跪在这些细节上: 1.输出格式, ...

  6. Machine Learning - XV. Anomaly Detection异常检測 (Week 9)

    http://blog.csdn.net/pipisorry/article/details/44783647 机器学习Machine Learning - Andrew NG courses学习笔记 ...

  7. canvas图形函数

    function drawStar(cobj,x, y, radius1, radius2, num, drawType, color) {//参数:画笔,圆心X.圆心Y,半径1,半径2,形状边,实心 ...

  8. 基于Office 365的随需应变业务应用平台

    作者:陈希章 发表于 2017年9月7日 这是我去年10月底在微软技术大会(Microsoft Ignite 2016) 上面的演讲主题,承蒙大家抬爱,也沾了前一场明星讲师的光,我记得会场几乎是满座. ...

  9. iOS开发之JSON转PLIST(把存储json格式的文件转换成plist文件)

    p.p1 { margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px "PingFang SC"; color: #586e75 } p.p2 ...

  10. Android项目实战(三十六):给背景加上阴影效果

    圆角背景大家应该经常用: 一个drawable资源文件  里面控制corner圆角 和solid填充色 <shape xmlns:android="http://schemas.and ...