最近项目上使用了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给还原回来

sonar-scanner的执行流程和对ClassLoader,动态代理的使用的更多相关文章

  1. 追源索骥:透过源码看懂Flink核心框架的执行流程

    li,ol.inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt, ...

  2. Tomcat笔记:Tomcat的执行流程解析

    Bootstrap的启动 Bootstrap的main方法先new了一个自己的对象(Bootstrap),然后用该对象主要执行了四个方法: init(); setAwait(true); load(a ...

  3. 透过源码看懂Flink核心框架的执行流程

    前言 Flink是大数据处理领域最近很火的一个开源的分布式.高性能的流式处理框架,其对数据的处理可以达到毫秒级别.本文以一个来自官网的WordCount例子为引,全面阐述flink的核心架构及执行流程 ...

  4. Dalvik模式下System.loadLibrary函数的执行流程分析

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78212010 Android逆向分析的过程中免不了碰到Android so被加固的 ...

  5. Java 代码执行流程

    Java 代码执行流程 类加载过程 加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载 类加载时机:代码使用到这个类时 验证阶段 &qu ...

  6. 步步深入:MySQL架构总览->查询执行流程->SQL解析顺序

    前言: 一直是想知道一条SQL语句是怎么被执行的,它执行的顺序是怎样的,然后查看总结各方资料,就有了下面这一篇博文了. 本文将从MySQL总体架构--->查询执行流程--->语句执行顺序来 ...

  7. 第二天 ci执行流程

    第二天 ci执行流程 welcome 页面 this this->load 单入口框架index.php 两个文件夹 system application定义 定义常亮路径 载入 codeign ...

  8. 轻量级前端MVVM框架avalon - 执行流程2

    接上一章 执行流程1 在这一大堆扫描绑定方法中应该会哪些实现? 首先我们看avalon能帮你做什么? 数据填充,比如表单的一些初始值,切换卡的各个面板的内容({{xxx}},{{xxx|html}}, ...

  9. [Java编程思想-学习笔记]第4章 控制执行流程

    4.1  return 关键字return有两方面的用途:一方面指定一个方法结束时返回一个值:一方面强行在return位置结束整个方法,如下所示: char test(int score) { if ...

随机推荐

  1. DHCP服务原理与搭建(Linux系统+路由器,二选一方案)

    大家都知道上网的最基本前提是要在终端上设置IP.子网掩码.网关.DNS等地址信息,在家里或者在办公室很多时候打开电脑后发现就可以上网,并没有手动设置IP.掩码.DNS地址也能上网,这是什么原因呢?其实 ...

  2. 【转】JavaScript 的装载和执行

    承接前面一篇文章<浏览器的渲染原理简介> ,本文来说下JavaScript的装载和执行. 通常来说,浏览器对于 JavaScript 的运行有两大特性: 1) 载入后马上执行 2) 执行时 ...

  3. log4net.Layout.PatternLayout 用 conversion 模式格式化日志事件【翻译】

    原文地址 log4net.Layout.PatternLayout,是一个灵活的布局,配置模式字符串. 线程安全.该类型的 Public static 成员对多线程操作是安全的.实例成员不保证线程安全 ...

  4. Apache Kafka学习 (二) - 多代理(broker)集群

    1. 配置server.properties > cp config/server.properties config/server-1.properties> cp config/ser ...

  5. webservice接口测试,使用SoapUI工具进行接口测试

    首先,接口使用cxf编写接口,测试工具使用SoapUI 5.2.1 安装之后是这样的图标: 测试操作步骤如下: (1)首先找到cxf-webservice.xml配置信息中地址,在浏览器中出入:htt ...

  6. 018-Go将磁盘目录实现简单的静态Web服务

    package main import( "net/http" ) func main(){ http.Handle("/", http.FileServer( ...

  7. SoapUI Pro Project Solution Collection-change the JDBC Request behavior

    change the jdbc request : 1.change the driver name,connection string,query string or assert. the obj ...

  8. Hadoop2.2.0分布式安装配置详解[3/3]

    测试启动 按照下面的每一步执行,执行完一定要看输出的信息,注意warn或error或fatal的情况.因为这都是可能是问题出现的地方.出现一个问题,不解决,可能就会影响接下来的测试.这才是真正的工作量 ...

  9. 永久关闭selinux

    selinux这东西,有时候真让人搞不懂. 临时关闭: setenforce 0 getenforce #查看状态是否是disabled 永久关闭: vim /etc/sysconfig/selinu ...

  10. 收藏清单: python测试数据生成及代码扫描最全工具列表

    Test Data manipulation 测试数据的操作和处理 faker - 生成假数据的python库 fake2db - 创建假数据库 ForgeryPy - 使用起来很简单的假数据生成库. ...