基于 SkyWalking Java Agent 8.8.0 版本

上一篇文章中我们重点分析了自定义类加载器 AgentClassLoader.initDefaultLoader() 部分,AgentClassLoader 初始化主要是定位 skywalking-agent.jar 所在目录以及成员变量 DEFAULT_LOADER 和 classpath 的初始化。

AgentClassLoader 主要负责查找插件和拦截器,其中插件位于 skywalking-agent.jar 目录下的 Config.Plugin.MOUNT 配置所在目录(默认目录是 plugins 和 activations)。

/*** Plugins finder. Use {@link PluginResourcesResolver} to find all plugins, and ask {@link PluginCfg} to load all plugin* definitions.*/
public class PluginBootstrap {private static final ILog LOGGER = LogManager.getLogger(PluginBootstrap.class);/*** load all plugins.** @return plugin definition list.*/public List<AbstractClassEnhancePluginDefine> loadPlugins() throws AgentPackageNotFoundException {// 1.初始化 AgentClassLoaderAgentClassLoader.initDefaultLoader();PluginResourcesResolver resolver = new PluginResourcesResolver();// 2.使用 AgentClassLoader 读取插件定义文件 skywalking-plugin.defList<URL> resources = resolver.getResources();if (resources == null || resources.size() == 0) {LOGGER.info("no plugin files (skywalking-plugin.def) found, continue to start application.");return new ArrayList<AbstractClassEnhancePluginDefine>();}for (URL pluginUrl : resources) {try {// 3.读取插件定义文件 skywalking-plugin.def 内容,封装成 PluginDefinePluginCfg.INSTANCE.load(pluginUrl.openStream());} catch (Throwable t) {LOGGER.error(t, "plugin file [{}] init failure.", pluginUrl);}}List<PluginDefine> pluginClassList = PluginCfg.INSTANCE.getPluginClassList();List<AbstractClassEnhancePluginDefine> plugins = new ArrayList<AbstractClassEnhancePluginDefine>();for (PluginDefine pluginDefine : pluginClassList) {try {LOGGER.debug("loading plugin class {}.", pluginDefine.getDefineClass());// 4.使用 AgentClassLoader 加载并实例化插件定义类AbstractClassEnhancePluginDefine plugin = (AbstractClassEnhancePluginDefine) Class.forName(pluginDefine.getDefineClass(), true, AgentClassLoader.getDefault()).newInstance();plugins.add(plugin);} catch (Throwable t) {LOGGER.error(t, "load plugin [{}] failure.", pluginDefine.getDefineClass());}}plugins.addAll(DynamicPluginLoader.INSTANCE.load(AgentClassLoader.getDefault()));return plugins;}}

AgentClassLoader初始化完成之后,接下来由 PluginResourcesResolver 负责调用 AgentClassLoader 读取所有插件定义文件(skywalking-plugin.def)

PluginResourcesResolver resolver = new PluginResourcesResolver();
// 2.使用 AgentClassLoader 读取插件定义文件 skywalking-plugin.def
List<URL> resources = resolver.getResources();

我们这里以tomcat插件为例看下插件定义文件的内容,tomcat插件位于 apm-sniffer/apm-sdk-plugin/tomcat-7.x-8.x-plugin/src/main/resources/skywalking-plugin.def 内容如下:

tomcat-7.x/8.x=org.apache.skywalking.apm.plugin.tomcat78x.define.TomcatInstrumentation
tomcat-7.x/8.x=org.apache.skywalking.apm.plugin.tomcat78x.define.ApplicationDispatcherInstrumentation

插件定义格式 pluginName=defineClass 的形式。

PluginResourcesResolver.getResources 方法

/*** Use the current classloader to read all plugin define file. The file must be named 'skywalking-plugin.def'*/
public class PluginResourcesResolver {private static final ILog LOGGER = LogManager.getLogger(PluginResourcesResolver.class);public List<URL> getResources() {List<URL> cfgUrlPaths = new ArrayList<URL>();Enumeration<URL> urls;try {// getResources(name) 方法内部会调用 AgentClassLoader 重写的 findResources 方法urls = AgentClassLoader.getDefault().getResources("skywalking-plugin.def");while (urls.hasMoreElements()) {URL pluginUrl = urls.nextElement();cfgUrlPaths.add(pluginUrl);// jar:file:/path/to/skywalking-java/skywalking-agent/plugins/tomcat-7.x-8.x-plugin-8.8.0.jar!/skywalking-plugin.def LOGGER.info("find skywalking plugin define in {}", pluginUrl);}return cfgUrlPaths;} catch (IOException e) {LOGGER.error("read resources failure.", e);}return null;}
}

这里很简单,就是调用 AgentClassLoader 的 getResources 方法,getResources 方法是在父类 ClassLoader 中定义的,根据类加载器的委托机制(可以看下 ClassLoader.getResources 的具体实现,这里就不赘述了),最后的会调用 AgentClassLoader 实现的 findResources 方法,得到资源的URL。

继续回到 PluginBootstrap.loadPlugins 方法内部,开始遍历资源的URL

for (URL pluginUrl : resources) {try {// 读取插件定义文件 skywalking-plugin.def 内容,封装成 PluginDefinePluginCfg.INSTANCE.load(pluginUrl.openStream());} catch (Throwable t) {LOGGER.error(t, "plugin file [{}] init failure.", pluginUrl);}
}

PluginCfg 是一个通过枚举实现的单例,主要负责读取插件定义文件(skywalking-plugin.def)的内容,按行读取封装成 PluginDefine,并放入成员变量 pluginClassList 中,PluginCfg 提供了 getPluginClassList 方法用于获取读取到的插件定义信息。

public enum PluginCfg {INSTANCE;private static final ILog LOGGER = LogManager.getLogger(PluginCfg.class);private List<PluginDefine> pluginClassList = new ArrayList<PluginDefine>();private PluginSelector pluginSelector = new PluginSelector();void load(InputStream input) throws IOException {try {BufferedReader reader = new BufferedReader(new InputStreamReader(input));String pluginDefine;while ((pluginDefine = reader.readLine()) != null) {try {if (pluginDefine.trim().length() == 0 || pluginDefine.startsWith("#")) {continue;}// 读取插件定义文件的每一行,比如 dubbo=org.apache.skywalking.apm.plugin.asf.dubbo.DubboInstrumentation// 将每一行的 name=class 封装成 PluginDefinePluginDefine plugin = PluginDefine.build(pluginDefine);pluginClassList.add(plugin);} catch (IllegalPluginDefineException e) {LOGGER.error(e, "Failed to format plugin({}) define.", pluginDefine);}}// 根据 Config.Plugin.EXCLUDE_PLUGINS 配置的插件名称排除部分插件pluginClassList = pluginSelector.select(pluginClassList);} finally {input.close();}}public List<PluginDefine> getPluginClassList() {return pluginClassList;}}

接着从 PluginCfg 中获取插件定义信息,调用 Class.forName 获取插件定义类的 Class对象。

/*** Plugins finder. Use {@link PluginResourcesResolver} to find all plugins, and ask {@link PluginCfg} to load all plugin* definitions.*/
public class PluginBootstrap {private static final ILog LOGGER = LogManager.getLogger(PluginBootstrap.class);/*** load all plugins.** @return plugin definition list.*/public List<AbstractClassEnhancePluginDefine> loadPlugins() throws AgentPackageNotFoundException {// 省略其他部分代码List<PluginDefine> pluginClassList = PluginCfg.INSTANCE.getPluginClassList();List<AbstractClassEnhancePluginDefine> plugins = new ArrayList<AbstractClassEnhancePluginDefine>();for (PluginDefine pluginDefine : pluginClassList) {try {LOGGER.debug("loading plugin class {}.", pluginDefine.getDefineClass());// 使用 AgentClassLoader 加载并实例化插件定义类AbstractClassEnhancePluginDefine plugin = (AbstractClassEnhancePluginDefine) Class.forName(pluginDefine.getDefineClass(), true, AgentClassLoader.getDefault()).newInstance();plugins.add(plugin);} catch (Throwable t) {LOGGER.error(t, "load plugin [{}] failure.", pluginDefine.getDefineClass());}}// 省略其他部分代码}
}

我们重点看下 Class.forName 部分,这里指定了类加载器为 AgentClassLoader.getDefault()

public static Class<?> forName(String name, boolean initialize, ClassLoader loader)throws ClassNotFoundException

Class.forName(defineClass, true, AgentClassLoader.getDefault()) 使用 AgentClassLoader 类加载器时,JVM底层会调用 ClassLoader.loadClass 方法,根据类加载器的委托机制,ClassLoader 的 loadClass 方法的内部实现会调用子类 AgentClassLoader 的 findClass 方法,因为 AgentClassLoader 的所有父加载器都不知道怎么去加载这些插件定义类,只有 AgentClassLoader 知道如何去找。

AgentClassLoader.findClass 实现如下:

/*** The <code>AgentClassLoader</code> represents a classloader, which is in charge of finding plugins and interceptors.*/
public class AgentClassLoader extends ClassLoader {// 省略其他部分代码@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {List<Jar> allJars = getAllJars();String path = name.replace('.', '/').concat(".class");for (Jar jar : allJars) {JarEntry entry = jar.jarFile.getJarEntry(path);if (entry == null) {continue;}try {URL classFileUrl = new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + path);byte[] data;try (final BufferedInputStream is = new BufferedInputStream(classFileUrl.openStream()); final ByteArrayOutputStream baos = new ByteArrayOutputStream()) {int ch;while ((ch = is.read()) != -1) {baos.write(ch);}data = baos.toByteArray();}// defineClass 方法用于将类的字节数组转换成类的 Class 对象return processLoadedClass(defineClass(name, data, 0, data.length));} catch (IOException e) {LOGGER.error(e, "find class fail.");}}throw new ClassNotFoundException("Can't find " + name);}// 省略其他部分代码
}

获取到插件定义类的Class对象之后,然后调用 Class.newInstance 方法实例化这些插件定义类,再将插件定义类对象强转成 AbstractClassEnhancePluginDefine 类对象,这里可以看出所有的插件定义类都是 AbstractClassEnhancePluginDefine 的子类。

到这里完成了插件的加载过程,主要是插件的查找和实例化。其中 AgentClassLoader 在插件加载过程扮演着非常重要的角色,我们之前可能只是在一些书上看到过 Java 的自定义类加载器,可以说 SkyWalking Java Agent 对类加载器委托机制理论的实战。

那么插件定义类的作用是什么呢,我将在下一篇进行介绍,敬请关注。

笔者在阅读源码过程对部分代码添加的注释已经提交到 GitHub 上了,具体参见 https://github.com/geekymv/skywalking-java/tree/v8.8.0-annotated

更多精彩内容请关注公众号 geekymv,喜欢请分享给更多的朋友哦」如有问题,欢迎交流。

Apache SkyWalking Java Agent 05-插件加载机制(下)相关推荐

  1. Java虚拟机 —— 类的加载机制

    我们知道class文件中存储了类的描述信息和各种细节的数据,在运行Java程序时,虚拟机需要先将类的这些数据加载到内存中,并经过校验.转换.解析和初始化过后,最终形成可以直接使用的Java类型. 类从 ...

  2. 纸壳CMS的插件加载机制

    纸壳CMS是一个开源的可视化设计CMS,通过拖拽,在线编辑的方式来创建网站. GitHub https://github.com/SeriaWei/ZKEACMS.Core 欢迎Star,Fork,发 ...

  3. Android 插件化原理解析——插件加载机制

    上文 Activity生命周期管理 中我们地完成了『启动没有在AndroidManifest.xml中显式声明的Activity』的任务:通过Hook AMS和拦截ActivityThread中H类对 ...

  4. 解决URL存在特殊符号、异步线程池配置、动态加载lib下所有jar包

    一.解决URL存在特殊符号|{}?&.URL中包含%2F.URL中包含%5C import lombok.extern.slf4j.Slf4j; import org.springframew ...

  5. 源码解析 --skywalking agent 插件加载流程

    1. 插件 目前很多框架,都采用框架 + 插件的模式开发. 如DataX.FlinkX通过插件支持众多异构数据源, Skywalking通过插件实现针对很多软件如redis.mysql.dubbo等方 ...

  6. skywalking~插件加载

    插件加载的入口 new PluginFinder(new PluginBootstrap().loadPlugins()); 加载插件过程 public List<AbstractClassEn ...

  7. Skywalking-13:Skywalking模块加载机制

    模块加载机制 基本概述 Module 是 Skywalking 在 OAP 提供的一种管理功能特性的机制.通过 Module 机制,可以方便的定义模块,并且可以提供多种实现,在配置文件中任意选择实现. ...

  8. vscode安装swift插件_使用 Webpack 优化 VS Code 插件加载性能

    Webpack 这一 JS 模块打包神器相信大家都不陌生了.由于 VS Code 插件大部分也都是 JS/TS 代码 + 依赖库的形式,因此也可以使用 Webpack 打包,优化性能. 经过实测,经过 ...

  9. Android插件化原理—ClassLoader加载机制

    前面<Android 插件化原理学习 -- Hook 机制之动态代理>一文中我们探索了一下动态代理 hook 实现了 启动没有在 AndroidManifest.xml 中显式声明的 Ac ...

最新文章

  1. php函数默认参数不传值报错,php 关于函数参数的默认值
  2. Object.defineProperty的理解
  3. API网关Kong系列(二)部署
  4. 【CV】图像分析用 OpenCV 与 Skimage,哪一个更好?
  5. 您自己的MicroProfile Config来源
  6. python str函数isdigit、isdecimal、isnumeric的区别
  7. 数据结构排序系列详解之一 插入排序
  8. python矩阵_Python矩阵
  9. EL表达式和JSTL标签库
  10. 正态分布方法判别,独立样本T检验及Mann-Whitney U 检验操作
  11. 万能网页视频下载教程
  12. Fvuln-自动化web漏洞检测工具
  13. 接连倒闭失联的背后 传统健身房生意为什么突然就不行了?
  14. python中base函数_详细的python basemap中各函数的所有参量注释
  15. c#微信企业号开发之消息推送
  16. 苹果手机验真假_朋友说他用手机观察细胞结构,一开始我还以为是开玩笑的……...
  17. 〖TFS_CLUB社区〗-〖星荐官共赢计划〗~ 期待各位小伙伴的加入~
  18. 专注于企业元器件参数管理的物料库管理系统
  19. mybatis 自动填充无效_mybatisPlus踩坑之--自动填充
  20. 解析人工智能与人类智慧的求同存异

热门文章

  1. 情绪激动的时候最好不要做决定
  2. android 9.0修改默认壁纸
  3. 笔记本计算机涂硅脂,硅脂,教您怎么在电脑CPU上涂散热硅脂
  4. 解决:VBOX无法分配USB设备到虚拟电脑
  5. 猿(媛)来你也在这里!!
  6. 粉象生活宇漫:贴吧引流实战教程
  7. 15个强大的iPad应用程序推荐
  8. noi字符串11:潜伏者题解
  9. 查询每个学生的总成绩的各个科目的分数和姓名,按总成绩排序
  10. Symmetrix GK盘介绍