• sonar-scanner的执行流程和对ClassLoader,动态代理的使用


    最近项目上使用了sonarqube来提供静态代码检查的服务,在看sonar-scanner的源码的时候,发现sonar-scanner用来分析的jar包是从sonar的服务器上下载下来的,使用自定义的ClassLoader来加载这些从服务器上下载下来的jar包,然后使用了jdk的动态代理来创建了一个启动器类,然后使用这个启动器调用了sonar提供的Batch API启动了代码分析

    Sonar的scanner中对ClassLoader和JDK的动态代理的使用,是ClassLoader的一个比较典型的应用场景,本文会以sonar-scanner的源码分析来说明soanr-scanner是如何使用ClassLoader和JDK的动态代理的

    sonar的scanner是如何启动的

    不管是soanr-scanner的客户端,还是maven插件,gradle插件sonar-scanner执行分析的方式都是调用了sonar-scanner-api这个jar包中的类,创建了一个EmbeddedScanner来执行分析的,如果我们手动调用的话,代码大概是这样的:

    package com.jiaoyiping.baseproject.sonar;
    
    import org.sonarsource.scanner.api.EmbeddedScanner;
    import org.sonarsource.scanner.api.LogOutput;
    import org.sonarsource.scanner.api.ScanProperties;
    import org.sonarsource.scanner.api.StdOutLogOutput;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * Created with Intellij IDEA
     *
     * @author: jiaoyiping
     * Mail: jiaoyiping@gmail.com
     * Date: 2018/08/02
     * Time: 21:49
     * To change this template use File | Settings | Editor | File and Code Templates
     */
    
    public class SonarScannerDemo {
    
    private static LogOutput logOutput = new StdOutLogOutput();
    
    //sonar的配置
    private static Map<String, String> sonarPropertiesMap = new LinkedHashMap<String, String>() {{
        put("sonar.host.url", "http://192.168.1.101:9000/sonar");
        put("sonar.sourceEncoding", "UTF-8");
        put("sonar.login", "xxxxxxxx8ca15ed386d08ffac90ad4efdb9a3");
    
    }};
    
    //项目代码的配置
    private static Map<String, String> projectSettingMap = new LinkedHashMap<String, String>() {{
        put(ScanProperties.PROJECT_KEY, "abcdef");
        put(ScanProperties.PROJECT_BASEDIR, "D:\temp\stateless4j");
        put(ScanProperties.PROJECT_SOURCE_DIRS, "src\main\java");
        put(ScanProperties.PROJECT_SOURCE_ENCODING, "UTF-8");
        put("sonar.java.binaries", "target\classes");
        put("sonar.java.source", "src\main\java");
    
    }};
    
    public static void main(String[] args) {
        //使用sonar的分析器来分析代码
        //规则从服务器下载
        //分析的结果再上传到服务器上去
    
        EmbeddedScanner scanner = EmbeddedScanner.create("Gradle", "6.7.4", logOutput);
        scanner.addGlobalProperties(sonarPropertiesMap);
        scanner.start();
        scanner.execute(projectSettingMap);
    
    
    	}
    }    
    

    创建了一个EmbeddedScanner,然后设置全局属性,然后调用这个scanner的start方法,然后传入项目相关的一些属性来执行分析

    其中start方法的作用是使用上边的全局属性中的soanr服务器的信息,从服务器上下载相关的jar包,并使用JDK的动态代理来创建相应的启动器对象,所以,这一部分是我们主要要看的(soanr执行分析的部分,涉及到了一个在sonar中很重要的设计模式,就是visitor模式,这里不进行分析,以后的文章会分析这一部分)

    在EmbeddedScanner的create工厂方法里,创建了IsolatedLauncherFactory的实例,在IsolatedLauncherFactory 的createLauncher方法中,执行了下载jar包和使用动态代理创建launcher的方法

    接下来,我们看jarDownloader是如何下载jar包的:

    getbootstrapIndexDownloader.getIndex()会去获取可以下载的jar包的名称和hash值:

    我们用浏览器来调用这个地址,可以看到返回的内容就是jar包的名称和hash值

    fileCache.get()方法会调用ScannerFileDownloader的download方法将jar包下载下来(参考上边的代码截图)

    下载完jar包之后,就是根据下载的jar包来创建一个classLoader,其实就是创建了一个自定义的继承了UrlClassLoader的IsolatedClassloader,然后把我们下载下来的jar包转化为url,添加到calssLoader里边去,这样,我们从这个classLoader来加载对应的类的时候,就能加载到我们下载下来的jar包中的类(ClassLoader工作的方法是首先让父类去加载,父类加载不到,抛出异常的时候,再尝试调用自己的findClass去加载,但是sonar-scanner中的ClassLoader的实现偷了一个懒,直接将url添加到父类UrlClassLoader的url列表里去了,但是加载的效果是一样的)

    以下是IsolatedLauncherFactory的 createClassLoader方法

    接下来就是调用IsolatedLauncherProxy这个类的create方法来生成launcher了
    IsolatedLauncherFactory的createLauncher方法调用了这个类,使用了我们刚才生成的ClassLoader

    cl = createClassLoader(jarFiles, rules);
    IsolatedLauncher objProxy = IsolatedLauncherProxy.create(cl, IsolatedLauncher.class, launcherImplClassName, logger);
    

    launcherImplClassName通过定义在IsolatedLauncherFactory中的一个字符串常量,在构造方法中设置的

    IsolatedLauncherProxy的实现代码如下,是一个典型的使用JDK的动态代理的代码,通过传入的ClassLoader,和要生成的类的名称org.sonarsource.scanner.api.internal.batch.BatchIsolatedLauncher,我们生成了BatchIsolatedLauncher的实例,这个BatchIsolatedLauncher调用了sonar提供的Batch类,传入相应的参数,实现了代码的静态检查这个功能:

    package org.sonarsource.scanner.api.internal;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    import org.sonarsource.scanner.api.internal.cache.Logger;
    
    public class IsolatedLauncherProxy implements InvocationHandler {
      private final Object proxied;
      private final ClassLoader cl;
      private final Logger logger;
    
      private IsolatedLauncherProxy(ClassLoader cl, Object proxied, Logger logger) {
        this.cl = cl;
        this.proxied = proxied;
        this.logger = logger;
      }
    
      public static <T> T create(ClassLoader cl, Class<T> interfaceClass, String proxiedClassName, Logger logger) throws ReflectiveOperationException {
        Object proxied = createProxiedObject(cl, proxiedClassName);
        // interfaceClass needs to be loaded with a parent ClassLoader (common to both ClassLoaders)
        // In addition, Proxy.newProxyInstance checks if the target ClassLoader sees the same class as the one given
        Class<?> loadedInterfaceClass = cl.loadClass(interfaceClass.getName());
        return (T) create(cl, proxied, loadedInterfaceClass, logger);
      }
    
      public static <T> T create(ClassLoader cl, Object proxied, Class<T> interfaceClass, Logger logger) {
        Class<?>[] c = {interfaceClass};
        return (T) Proxy.newProxyInstance(cl, c, new IsolatedLauncherProxy(cl, proxied, logger));
      }
    
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ClassLoader initialContextClassLoader = Thread.currentThread().getContextClassLoader();
    
        try {
          Thread.currentThread().setContextClassLoader(cl);
          logger.debug("Execution " + method.getName());
          return method.invoke(proxied, args);
        } catch (UndeclaredThrowableException | InvocationTargetException e) {
          throw unwrapException(e);
        } finally {
          Thread.currentThread().setContextClassLoader(initialContextClassLoader);
        }
      }
    
      private static Throwable unwrapException(Throwable e) {
        Throwable cause = e;
    
        while (cause.getCause() != null) {
          if (cause instanceof UndeclaredThrowableException || cause instanceof InvocationTargetException) {
            cause = cause.getCause();
          } else {
            break;
          }
        }
        return cause;
      }
    
      private static Object createProxiedObject(ClassLoader cl, String proxiedClassName) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class<?> proxiedClass = cl.loadClass(proxiedClassName);
        return proxiedClass.newInstance();
      }
    }
    

    整个soanr-scanner的启动的流程就是上边写到的这些东西,设计的很巧妙,我们客户端使用的时候,只需要依赖一个 sonar-scanner-api即可,分析代码需要的jar包,会从sonar的服务器上去下载,然后本地分析完成之后的结果,又会上传到服务器上去进行图表展示,这样我们自己写的分析规则,只要上传到服务器上去即可,真正执行分析的时候,sonar-scanner会从服务器上去下载,因为有一部分jar包要到服务器上去下载,而这些jar包又是不固定的,有可能会变化,这样,使用一个自定义的ClassLoader来加载这些jar包就是很自然的事情了

    而使用JDK的动态代理,我们不仅创建了一个使用了之前的ClasLloader加载的类的对象,而且在这个对象的方法执行前设置了ContextClassLoader,在方法执行后,又将之前的CalssLoader给还原回来

  • 相关阅读:
    Redis设计与实现第一部分:第5章:Redis 跳跃表
    根据临时表修改主表的某字段数据根据主表的主键
    Redis设计与实现第一部分:第2章:简单动态字符串SDS
    Redis
    MySQL的访问控制与用户管理
    MySQL字符集和语言的基础知识
    生成日志文件
    Python进阶09 动态类型
    Python进阶08 异常处理
    Python进阶07 函数对象
  • 原文地址:https://www.cnblogs.com/jiaoyiping/p/9691620.html
Copyright © 2020-2023  润新知