zoukankan      html  css  js  c++  java
  • 7. jvm-sandbox之服务接口扫描

    服务接口扫描

    一、概述

    前段时间在测试环境部署了jvm-sandbox-repeater,成功录制到请求记录。鉴于项目中出现过业务漏测的情况(有服务的新接口未覆盖到),所以想实现一个接口覆盖的功能。
    主要原理是通过对比录制的接口记录和扫描到的服务接口差异,从而知道在项目测试时间段内,哪些接口没有被覆盖到。

    二、编写Module

    2.1 获取SpringClassLoad和controller类的路径

    这里当sandbox被加载完成时,新建了两个EventWatchBuilder。
    一个针对refresh方法,获取到spring的加载类
    另一个针对buildDefaultBeanName方法,获取到带“controller”的类路径名称集合
    代码如下:

    @Override
        public void loadCompleted() {
             new EventWatchBuilder(moduleEventWatcher)
                    .onClass("org.springframework.context.support.AbstractApplicationContext")
                    .onBehavior("refresh")
                    .onWatching()
                    .withCall()
                    .onWatch(new AdviceListener() {
                        @Override
                        protected void before(Advice advice) throws Throwable {
                            if (springClassLoadIsNull()) {
                                setSpringClassLoad(advice.getBehavior().getDeclaringClass().getClassLoader());
                            }
                        }
                    });
            new EventWatchBuilder(moduleEventWatcher)
                    .onClass("org.springframework.context.annotation.AnnotationBeanNameGenerator")
                    .onBehavior("buildDefaultBeanName")
                    .onWatching()
                    .withCall()
                    .onWatch(new AdviceListener() {
                        @Override
                        protected void before(Advice advice) throws Throwable {
                            Object o=advice.getParameterArray()[0];//BeanDefinition
                            IBeanDefinition beanDefinition = InterfaceProxyUtils.puppet(IBeanDefinition.class, o);
                            String s = beanDefinition.getBeanClassName();
                            if(StringUtils.containsIgnoreCase(s,"controller")){
                                if(!controllerPackageNames.contains(s)){
                                    lifeCLogger.debug("===="+s);
                                    controllerPackageNames.add(s);
                                }
                            }
                        }
                    });
        }
    

    2.2 获取类路径名称集合

    这里编写了一个Command,给调用服务返回类路径名称集合

     @Command("controllerPackageNames")
        public void controllers(final PrintWriter writer){
            lifeCLogger.debug("controllerPackageNames");
            writer.println(JSONObject.toJSONString(controllerPackageNames));
            writer.flush();
        }
    

    2.3 组建接口信息

    有了加载类和类路径后,就可以通过反射,在controller类对象中拿到所有注解信息,再做一些处理,就可以得到完整的接口信息了。

    2.3.1 编写一个Command

    这里编写了一个处理单个controller类的Command,具体遍历controller类路径名称集合的逻辑在另外的调用服务中实现。

     @Command("uris")
        public void uris(final Map<String, String> param, final PrintWriter writer) {
            final String appName = getParameter(param, "appName");
            final String env = getParameter(param, "env");
            final String packageName = getParameter(param, "packageName");
            final String version = getParameter(param, "version");
            if (springClassLoadIsNull()) {
                lifeCLogger.debug("还没有获取到SpringClassLoad");
                writer.println("error: SpringClassLoad is null");
            } else {
                if (null == packageName && null == appName && null == env) {
                    lifeCLogger.debug("appName,env,packageName 有一个参数为空");
                    writer.println("error: one of params(appName env packageName) is null");
                } else {
                    lifeCLogger.debug("当前spring类加载器 SpringClassLoad :{}", this.springClassLoad.getClass().getName());
                    lifeCLogger.debug("generate uris for {} - {} begin", env, appName);
                    try {
                        Class<?> targetClass = this.springClassLoad.loadClass(packageName);
                        //获取basePath
                        String basePath = takeBasePath(targetClass);
                        //遍历method获取最终的结果
                        List<Map<String, String>> resultList = traversalMethods(targetClass, basePath, appName, env, packageName,version);
                        writer.println(JSONObject.toJSONString(resultList));
                        lifeCLogger.debug("generate uris for {} - {} result: {} ", env, appName, JSONObject.toJSONString(resultList));
                        lifeCLogger.debug("generate uris for {} - {} end ", env, appName);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                        lifeCLogger.debug(e.getMessage());
                    } catch (Exception e) {
                        e.printStackTrace();
                        lifeCLogger.debug(e.getMessage());
                    } finally {
                        writer.flush();
                    }
                }
    
            }
    
        }
    

    2.3.2 获取Base路径的实现

    public String takeBasePath(Class<?> targetClass) {
            Annotation[] annotations=targetClass.getAnnotations();
            for(Annotation annotation:annotations){
                if(annotation.annotationType().getSimpleName().equals("RequestMapping")){
                    String[] s=(String[]) InstanceUtils.doMethodByName("value",annotation);
                    lifeCLogger.debug(JSONObject.toJSONString(s));
                    String basePath=s[0];
                    //处理一下basePath  保证格式是/123/456
                    if (!basePath.startsWith("/")) {
                        basePath = "/" + basePath;
                    }
                    if (basePath.endsWith("/")) {
                        basePath = basePath.substring(0, basePath.length() - 1);
                    }
                    if("/".equals(basePath)){
                        basePath="";
                    }
                    return basePath;
                }
            }
            return "";
        }
    

    2.3.3 获取子路径实现

     public Map<String, String> handleSubPath(Method method, String basePath, String appName, String env, String packageName,String version) {
            Map<String, String> resultMap = new HashMap<>();
    
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                if (annotation.annotationType().getSimpleName().equals("RequestMapping")) {
                    resultMap.put("appName", appName);
                    resultMap.put("env", env);
                    resultMap.put("packageName", packageName);
                    resultMap.put("version",version);
                    String[] values = (String[]) InstanceUtils.doMethodByName("value", annotation);
                    String path = values[0];
                    if(null!=path){
                        if(!path.startsWith("/")){
                            path="/"+path;
                        }
                        if(path.endsWith("/")){
                            path = path.substring(0, path.length() - 1);
                        }
                        if("/".equals(path)){
                            resultMap.put("uri", basePath);
                        }else{
                            resultMap.put("uri", basePath + path);
                        }
                    }else{
                        resultMap.put("uri", basePath);
                    }
                    Object requestMethods_object = InstanceUtils.doMethodByName("method", annotation);
                    String re = JSONObject.toJSONString(requestMethods_object);
                    List<RequestMethod> requestMethods = JSONObject.parseArray(re, RequestMethod.class);
                    StringBuffer methodString = new StringBuffer();
                    if (requestMethods.size() > 0) {//处理method格式
                        for (int i = 0; i < requestMethods.size(); i++) {
                            methodString.append(requestMethods.get(i));
                            if (i != requestMethods.size() - 1) {
                                methodString.append(",");
                            }
                        }
                    }
                    resultMap.put("method", methodString.length() > 0 ? methodString.toString() : "default");
                }
                if (annotation.annotationType().getSimpleName().equals("PostMapping")) {
                    resultMap.put("appName", appName);
                    resultMap.put("env", env);
                    resultMap.put("packageName", packageName);
                    resultMap.put("version",version);
                    resultMap.put("method", "post");
                    String[] values = (String[]) InstanceUtils.doMethodByName("value", annotation);
                    String path = values[0];
                    if (null != path && !path.startsWith("/")) {
                        resultMap.put("uri", basePath + "/" + path);
                    } else {
                        resultMap.put("uri", basePath + path);
                    }
                }
    
                if (annotation.annotationType().getSimpleName().equals("GetMapping")) {
                    resultMap.put("appName", appName);
                    resultMap.put("env", env);
                    resultMap.put("packageName", packageName);
                    resultMap.put("version",version);
                    resultMap.put("method", "get");
                    String[] values = (String[]) InstanceUtils.doMethodByName("value", annotation);
                    String path = values[0];
                    if (null != path && !path.startsWith("/")) {
                        resultMap.put("uri", basePath + "/" + path);
                    } else {
                        resultMap.put("uri", basePath + path);
                    }
                }
            }
            return resultMap;
        }
    

    三、结果

    3.1接口信息

    接口信息

    3.2 接口覆盖情况

    这里利用钉钉群消息功能,将汇总消息发送通知到项目群中
    钉钉通知

    四、总结

    1.  接口覆盖不如代码覆盖精确,但是也可以避免主功能漏测。
    2. 通过测试环境中录制的记录,也可以分析出测试了哪些场景。
    3. 上面只给出了Module的编写方法,关于调用服务的代码比较简单,这里没有给出。
    

    五、参考

    Spring组件注册注解之@ComponentScan,@ComponentScans

    本文来自博客园,作者:月色深潭,交流群:733423266,转载请注明原文链接:https://www.cnblogs.com/moonpool/articles/15122905.html

  • 相关阅读:
    Using Flex swf to load html into frames(转)
    wan886网址导航 Bo9bo高清电影 两个图标 怎么都删不掉
    Flex的新插件BluePrint
    开源——需要分享共享的无私精神
    Flex相关的开发例子与资源收集(转)
    在FLEX中真正的隐藏一个组件,隐藏后不占据自身的位置. (转)
    显示属性里没有桌面选项卡解决办法
    FLEX如何读取XML文件.(转)
    ACTIONSCRIPT技巧,工具,API大收集(国外资源,转载)
    The Top 10 Things You Should Know About Flex (转)
  • 原文地址:https://www.cnblogs.com/moonpool/p/15122905.html
Copyright © 2011-2022 走看看