「工具」Dubbo测试工具的设计和实现

##背景

在研发或测试过程中,经常遇到RPC接口的测试,为此我们写了大量的单元测试用例侵入在系统工程中繁琐的创建接口和测试数据占用了大量的时间为了提高测试效率,开发了FreeFly-Remote-API系统,该系统旨在用通过简单的操作方式实现 dev,qa 甚至online的RPC测试,来释放开发和测试人员的双手。

##设计目标

1、无侵入性
严格保证系统独立,且拒绝在任何RPC业务服务中有嵌入相关的代码

2、易用性
可视化界面简单,配置简单,能够让任何开发和测试人员便捷使用

3、高效性
能够快速返回待查询接口相关信息,并利用缓存进行数据临时存储

##设计思路图示

业务流转图示

##业务步骤和项目技术栈

整个过程比较简单,具体流程如下描述:

1、通过输入pom依赖配置获取相关及依赖jar包,并下载到服务本地
2、通过java反射和类加载对jar包进行接口信息解析
3、前端按照解析结构输入参数,并从zookeeper拿取服务地址
4、远程请求获取数据集展示

主要技术栈:

maven依赖:wagon-ssh,aether (具体描述见下方依赖说明)
java: 反射,类加载
注册中心:zookeeper
RPC服务框架:dubbo
前端结构暂时:jsTree


##项目主要代码描述:

1.pom依赖信息

     <dependency>
            <groupId>org.apache.maven.wagon</groupId>
            <artifactId>wagon-ssh</artifactId>
            <version>${wagonVersion}</version>
      </dependency>
      <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-api</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-util</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-impl</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-connector-basic</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-transport-file</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-transport-http</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.aether</groupId>
            <artifactId>aether-transport-wagon</artifactId>
            <version>${aetherVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-aether-provider</artifactId>
            <version>${mavenVersion}</version>
        </dependency>

【Aether用于在自己的应用中集成Mavne的功能,包括依赖计算、包的分发,对本地和远程仓库的访问,它设计时考虑了对各种类型的依赖包管理仓库的抽象,因此也可以进行扩展以支持其他类似的工具】
以上引述可以理解是对maven仓库功能的一个支持工具,在本系统中我们会用到该工具获取jar包版本,jar包依赖关系 和jar包下载的功能

jar包下载相关代码示例:[ jar包下载入参结构体]

public class MavenParams {
	
	/**
	 * jar包在maven仓库中的groupId
	 */
	private String groupId;
	/**
	 * jar包在maven仓库中的artifactId
	 */
	private String artifactId;
	/**
	 * jar包在maven仓库中的version
	 */
	private String version;

	/**
	 * 登录远程maven仓库的用户名,若远程仓库不需要权限,设为null,默认为null
	 */
	private String username=null;
	/**
	 * 登录远程maven仓库的密码,若远程仓库不需要权限,设为null,默认为null
	 */
	private String password=null;
	
	
	public MavenParams() {
		super();
	}



	public MavenParams(String groupId, String artifactId) {
		super();
		this.groupId = groupId;
		this.artifactId = artifactId;
	}
	
	public MavenParams(String groupId, String artifactId, String username,
					   String password) {
		super();
		this.groupId = groupId;
		this.artifactId = artifactId;
		this.username = username;
		this.password = password;
	}

	public MavenParams(String groupId, String artifactId, String version,
					   String username, String password) {
		super();
		this.groupId = groupId;
		this.artifactId = artifactId;
		this.version = version;
		this.username = username;
		this.password = password;
	}

	public String getGroupId() {
		return groupId;
	}
	public void setGroupId(String groupId) {
		this.groupId = groupId;
	}
	public String getArtifactId() {
		return artifactId;
	}
	public void setArtifactId(String artifactId) {
		this.artifactId = artifactId;
	}
	public String getVersion() {
		return version;
	}
	public void setVersion(String version) {
		this.version = version;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}

jar包下载相关代码示例:[ :获取符合要求的版本号]

    public  List<Version> getAllVersions(MavenParams params) throws VersionRangeResolutionException {
        String groupId = params.getGroupId();
        String artifactId = params.getArtifactId();
        RepositorySystem repoSystem = Booter.newRepositorySystem();
        RepositorySystemSession session = Booter.newRepositorySystemSession(repoSystem);
        Artifact artifact = new DefaultArtifact(groupId + ":" + artifactId + ":[0,)");
        VersionRangeRequest rangeRequest = new VersionRangeRequest();
        rangeRequest.setArtifact(artifact);
        rangeRequest.setRepositories(Booter.newRepositories(repoSystem, session));
        VersionRangeResult rangeResult = repoSystem.resolveVersionRange(session, rangeRequest);
        List<Version> versions = rangeResult.getVersions();
        return versions;
    }

jar包下载相关代码示例:[ 下载指定版本号的jar包]

 /**
     * 从指定maven地址下载指定jar包
     * @throws ArtifactResolutionException
     */
    public File DownLoad(MavenParams params) throws Exception {

        String groupId = params.getGroupId();
        String artifactId = params.getArtifactId();
        String version = params.getVersion();
        RepositorySystem repoSystem = Booter.newRepositorySystem();
        RepositorySystemSession session = Booter.newRepositorySystemSession(repoSystem);
        Artifact artifact = new DefaultArtifact(groupId + ":" + artifactId + ":" + version);
        //下载当前jar包
        File file = downJar(artifact, repoSystem, session);
        return file;
    }

  private static File downJar(Artifact artifact, RepositorySystem repoSystem, RepositorySystemSession session) throws ArtifactResolutionException {

        ArtifactRequest artifactRequest = new ArtifactRequest();
        artifactRequest.setRepositories(Booter.newRepositories(repoSystem, session));
        artifactRequest.setArtifact(artifact);
        repoSystem.resolveArtifact(session, artifactRequest);
        String basePath = session.getLocalRepository().getBasedir().getPath() + "/" + artifact.getGroupId().replace(".", "/") + "/" + artifact.getArtifactId() + "/" + artifact.getVersion();
        String jarName = artifact.getArtifactId() + "-" + artifact.getVersion() + ".jar";
        File file = FileSearch.findFiles(basePath, jarName);
        return file;
    }


 /**
     * 递归查找文件
     * @param baseDirName  查找的文件夹路径
     * @param targetFileName  需要查找的文件名
     */
    public static File findFiles(String baseDirName, String targetFileName) {

        File file = null;
        File baseDir = new File(baseDirName);       // 创建一个File对象
        if (!baseDir.exists() || !baseDir.isDirectory()) {  // 判断目录是否存在
            logger.info("文件查找失败:" + baseDirName + "不是一个目录!");
        }
        String tempName = null;
        //判断目录是否存在
        File tempFile;
        File[] files = baseDir.listFiles();
        for (int i = 0; i < files.length; i++) {
            tempFile = files[i];
            if(tempFile.isDirectory()){
                 file = findFiles(tempFile.getAbsolutePath(), targetFileName);
                if (file != null) {
                    return  file;
                }
            }else if(tempFile.isFile()){
                tempName = tempFile.getName();
                if(tempName.equals(targetFileName)){
                    return tempFile.getAbsoluteFile();
                }
            }
        }
        return file;
    }

jar包下载相关代码示例:[ 获取相关依赖的jar包]

private static List<Artifact> filterDependencyJar(Artifact artifact, RepositorySystem repoSystem, RepositorySystemSession session,   List<Artifact> dependencyJarNameList) throws Exception {

        ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest();
        descriptorRequest.setArtifact(artifact);
        descriptorRequest.setRepositories(Booter.newRepositories(repoSystem, session));
        ArtifactDescriptorResult descriptorResult = repoSystem.readArtifactDescriptor(session, descriptorRequest);
        for (Dependency dependency : descriptorResult.getDependencies()) {
            Artifact dependencyArtifact = dependency.getArtifact();
            String groupId = dependencyArtifact.getGroupId();
            //只获取有相关联关系的jar,第三方jar不在考虑范围
            if (groupId.startsWith("com.xxx") || groupId.startsWith("qd") || groupId.startsWith("platform")) { 
       
                if (dependencyJarNameList.contains(dependencyArtifact)) continue;
                dependencyJarNameList.add(dependencyArtifact);
                //递归获取内嵌的依赖包
                filterDependencyJar(dependencyArtifact,repoSystem,session,dependencyJarNameList);
            }
        }
        return dependencyJarNameList;
    }

dubbo服务请求代码示例:[ 注册中心初始化方法]

 private GenericService initDubboRegister(String interfaceName,String zkEnvKey){
        Map<String,String> registerUrlMap = ApiPropertiesClient.getRegisterUrl();
        // 当前应用配置
        ApplicationConfig application = new ApplicationConfig();
        application.setName("remote-api-test");
        // 连接注册中心配置
        RegistryConfig registry = new RegistryConfig();
        registry.setProtocol("zookeeper");
        registry.setAddress(registerUrlMap.get(zkEnvKey));
        // 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接
        // 引用远程服务
        ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
        reference.setApplication(application);
        reference.setRegistry(registry); // 多个注册中心可以用setRegistries()
        reference.setInterface(interfaceName);//"com.qding.member.service.IMemberAddressRpcService"
        reference.setGeneric(true);
        // 和本地bean一样使用xxxService
        GenericService genericService = reference.get(); // 注意:此代理对象内部封装了所有通讯细节,对象较重

        return genericService;
    }

dubbo服务请求代码示例: dubbo泛化调用

  public Object dubboInvokeByParam (String zkEnvKey,String interfaceName, String methodName,String[] pojoName, Object[] paramValueArray) {

        GenericService genericService =  initDubboRegister(interfaceName,zkEnvKey);
        //	用Map表示POJO参数,如果返回值为POJO也将自动转成Map
        //	如果返回POJO将自动转成Map
        Object result  = genericService.$invoke(methodName, pojoName, paramValueArray);
        return result;
    }

##最终效果

初始界面
界面提供 jar包下载所需:groupId,artifactId,version配置输入界面

接口及结构体展示
指定接口请求响应结构解析

选择测试环境并输入相应参数值返回测试结果
测试返回结果集

测试用例进行保存
可对指定接口保存多个测试用例

以上就是该测试工具的实现过程,第一次在这写东西,如有不妥观者海涵!!

Doe 发布 [V1.0.0] 前段时间排查某问题的时候,想要快速知道某些dubbo接口(三无)的响应结果,但不想启动项目(因为这些项目不是你负责的,不会部署而且超级笨重),也不想新建一个dubbo客户端项目(占地方),也不想开telnet客户端连接口(麻烦而且有限制)。所以扣了dubbo的netty模块源码,封装了个收发客户端集成一个工具,可以快速调试dubbo接口。源码地址:https://github.com/VIPJoey/doe 极简模式 普通模式 目录结构 mmc-dubbo-api 接口项目,主要用于测试。 mmc-dubbo-provider dubbo提供者项目,主要用于测试。 mmc-dubbo-doe 主项目,实现dubbo接口调试。 deploy 部署文档 功能特性 极简模式:通过dubbo提供的telnet协议收发数据。 普通模式:通过封装netty客户端收发数据。 用例模式:通过缓存数据,方便下一次操作,依赖普通模式。 增加依赖:通过调用maven命令,下载jar包和热加载到系统,主要用来分析接口方法参数,主要作用在普通模式。 依赖列表:通过分析pom文件,展示已经加载的jar包。 其它特性 springboot 整合 redis,支持spring el 表达式。 springboot 整合 thymeleaf。 springboot 整合 logback。 netty rpc 实现原理。 开发环境 jdk 1.8 maven 3.5.3 dubbo 2.6.1 lombok 1.16.20 idea 2018 windows 7 安装步骤 安装jdk 安装maven,并设置好环境变量,仓库目录。 进入mmc-dubbo-api目录,执行mvn clean install命令,省api的jar包。 进入mmc-dubbo-doe目录,执行mvn clean install 命令,在target目录生成dubbo-doe-1.0.0-RELEASE.jar 在F盘(可以任意盘)创建目录F:\app\doe 把dubbo-doe-1.0.0-RELEASE.jar拷贝到F:\app\doe 把deploy目录中的所有文件拷贝到F:\app\doe 如果您电脑安装了git bash,可以在bash窗口运行 ./deploy.sh start,否则如果没有安装git bash,只能打开cmd切换到F:\app\doe目录,然后执行java -jar dubbo-doe-1.0.0-RELEASE.jar --spring.profiles.active=prd 打开浏览器,访问地址:http://localhost:9876/doe/home/index 全剧终
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值