Apache SkyWalking Java Agent 05-插件加载机制(下)

基于 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.初始化 AgentClassLoader
        AgentClassLoader.initDefaultLoader();

        PluginResourcesResolver resolver = new PluginResourcesResolver();
       // 2.使用 AgentClassLoader 读取插件定义文件 skywalking-plugin.def
        List<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 内容,封装成 PluginDefine
                PluginCfg.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 内容,封装成 PluginDefine
     PluginCfg.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 封装成 PluginDefine
                    PluginDefine 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 {
      // 省略其他部分代码
    
    @Override
    protected 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,喜欢请分享给更多的朋友哦」如有问题,欢迎交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: apache-skywalking-apm-es7-8.5.0.tar.gz 是一个开源的应用性能监控(APM)平台,使用 Java 开发,适用于云原生和基于容器化的微服务架构。它可以监控应用程序的运行状况、性能指标和错误信息,从而帮助开发人员可以更快、更准确的定位问题,提高应用可用性和性能。此版本适用于 Elasticsearch 7.x 版本,并包含新增功能、修复问题和性能优化。在使用前需要对应的技术要求和部署方式进行学习和了解。此外,用户也可以参考官方文档和社区进行深入学习和交流。apache-skywalking-apm-es7-8.5.0.tar.gz 是一个功能强大的开源工具,可以帮助开发人员更好地监控和管理分布式应用程序,提高开发效率和应用稳定性。 ### 回答2: Apache Skywalking是一款开源的应用性能管理工具,能够帮助用户监测和优化应用程序的运行。其中,apm-es7-8.5.0.tar.gz是Apache Skywalking的一个版本,其中包含了基于ElasticSearch 7.x的数据存储技术,能够实现更高效、更可靠的数据存储和检索。 在使用apm-es7-8.5.0.tar.gz版本的Apache Skywalking时,用户可以通过简单的配置和集成,实现对各种应用程序的监测和优化。该工具提供了丰富的监测指标和报告,能够深入分析涵盖多种技术领域的应用程序性能状况,包括分布式追踪、服务依赖关系、异常跟踪、性能瓶颈等方面。 此外,apm-es7-8.5.0.tar.gz版本的Apache Skywalking通过对数据的大规模处理和分析,提供了对系统整体性能的深入洞察。用户可以通过可视化报告和定制化的警报机制,及时地发现和解决应用程序中的错误和问题,进一步提升应用程序的稳定性和效率。 总之,apm-es7-8.5.0.tar.gz版本的Apache Skywalking是一款高效可靠的应用性能管理工具,为用户带来了全面的性能监测和优化体验,是提升应用程序效率和用户体验的重要工具。 ### 回答3: apache-skywalking-apm-es7-8.5.0.tar.gz是一个基于Apache SkyWalking的应用程序性能管理工具。它为用户提供了深入了解其应用程序的性能和行为的能力,帮助用户识别问题并优化其应用程序。该工具支持多种语言和框架,如Java和.NET,还支持多种数据库和消息队列。它具有灵活的插件架构,从而使用户能够自定义和扩展其功能。 这个工具还具有将数据导出到不同数据源的能力,例如Elasticsearch和Kafka,使其可与用户的现有解决方案无缝集成。此外,它还支持多样的告警方式,包括邮件和Webhook。 总之,apache-skywalking-apm-es7-8.5.0.tar.gz是一款强大的性能管理工具,能够帮助用户更好地了解其应用程序的性能和行为,识别问题并优化其应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值