1. 插件

目前很多框架,都采用框架 + 插件的模式开发。

如DataX、FlinkX通过插件支持众多异构数据源, Skywalking通过插件实现针对很多软件如redis、mysql、dubbo等方法执行信息采集。

本文针对 skywalking agent 插件加载流程进行源码解析,理解插件的接口定义、加载机制

针对skywalking agent的插件开发指导,请参考:https://github.com/apache/skywalking/blob/master/docs/en/guides/Java-Plugin-Development-Guide.md

1.1. 插件介绍

插件Plugin(Plug-in, addin, add-in, addon或add-on)是一种计算机应用程序,它和主应用程序(host application)互相交互,以提供特定的功能。

插件是一种可以热插拔的(动态安装和卸载),可以实现一定功能性并且对目前现有运行系统不会产生任何影响的一种松散耦合的设计模式,而且易扩展,可以让更多的开发者参与进来,让产品自身的功能更加丰富彩,它也可以通过动态的安装组合,实现不同的产品架构。

1.2. 插件机制

主应用程序提供给插件可以使用的服务,让插件在主应用程序中注册插件本身,以及和插件进行数据交换的协议。

插件依赖于主应用程序提供的这些服务,通常不能独立运行。相反地,主应用程序和插件是分离的,这就使得我们可以不改变主应用程序而动态增加或更新插件。

主应用程序提供一套插件接口规范及插件开发流程,允许第三方编写插件和主应用程序交互。

2. 源码解析环境

基于skywalking 8.3.0版本的源代码进行分析。

步骤如下:

  1. 将skywalking的apm-sniffer导入IntelliJ IDEA中

  2. 在project中新增一个java module

  3. 在run/debug 配置的VM options中增加javaagent参数,具体如下:-javaagent: 具体路径\skywalking-agent.jar -Dskywalking.agent.servicename=SourceDebug -Dskywalking.agent.applicationcode=SourceDebug -Dskywalking.collector.backend_service=IP:11800 -Dfile.encoding=UTF-8

注意

  • 使用 IntelliJ IDEA 的菜单 File / New / Module 或 File / New / Module from Existing Sources ,保证 新项目和 skywalking 项目平级。这样,才可以使用 IntelliJ IDEA 调试 Agent 。详细请参考:调试环境搭建

  • 将具体路径调整为skywalking-agent.jar所在的目录,将IP替换为部署skywalking server的服务器IP

3. skywalking agent的插件加载

插件加载的时序图如下:



具体插件加载流程如下:

  1. 在javaagent的入口类即SkyWalkingAgent中,创建PluginFinder对象

    
    try {
                pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
            } catch (AgentPackageNotFoundException ape) {
                LOGGER.error(ape, "Locate agent.jar failure. Shutting down.");
                return;
            } catch (Exception e) {
                LOGGER.error(e, "SkyWalking agent initialized failure. Shutting down.");
                return;
            }

     

  2. 调用PluginBootstrap实例的loadPlugins()加载插件

  3. loadPlugins()首先初始化AgentClassLoader对象,并根据AgentPackagePath类,找到skywalking-agent.jar所在的目录,即agent的部署目录,将agent目录下的plugins、activations加入到classpaths中。 注意:plugins目录下存储所有agent的插件,activations目录下存储toolkit插件如log4j,logback

  4. 获取所有插件定义的路径集合,实际代码及获取的路径集合如下图

    public List<URL> getResources() {
            List<URL> cfgUrlPaths = new ArrayList<URL>();
            Enumeration<URL> urls;
            try {
                urls = AgentClassLoader.getDefault().getResources("skywalking-plugin.def");
                while (urls.hasMoreElements()) {
                    URL pluginUrl = urls.nextElement();
                    cfgUrlPaths.add(pluginUrl);
                    LOGGER.info("find skywalking plugin define in {}", pluginUrl);
                }
                return cfgUrlPaths;
            } catch (IOException e) {
                LOGGER.error("read resources failure.", e);
            }
            return null;
        }

  5. 循环resources, 针对每一个插件,处理如下:

读取插件的skywalking-plugin.def定义

将每一行转换为一个插件定义PluginDefine实例,并将实例添加到pluginClassList中

通过pluginSelector.select排除配置EXCLUDE_PLUGINS中定义的插件

    6. 循环pluginClassList,将每一个插件转换为AbstractClassEnhancePluginDefine的实例,添加到plugins中

List<AbstractClassEnhancePluginDefine> plugins = new ArrayList<AbstractClassEnhancePluginDefine>();
        for (PluginDefine pluginDefine : pluginClassList) {
            try {
                LOGGER.debug("loading plugin class {}.", pluginDefine.getDefineClass());
                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());
            }
        }

    7. 通过DynamicPluginLoader的load方法,通过SPI的方式发现并实例化其它插件,并添加到plugins中


public List<AbstractClassEnhancePluginDefine> load(AgentClassLoader classLoader) {
        List<AbstractClassEnhancePluginDefine> all = new ArrayList<AbstractClassEnhancePluginDefine>();
        for (InstrumentationLoader instrumentationLoader : ServiceLoader.load(InstrumentationLoader.class, classLoader)) {
            List<AbstractClassEnhancePluginDefine> plugins = instrumentationLoader.load(classLoader);
            if (plugins != null && !plugins.isEmpty()) {
                all.addAll(plugins);
            }
        }
        return all;
    }

   8. 到此,插件加载完毕,后面的流程是skywalking agent构建byteBuddy,将所有插件增强的类注册到AgentBuilder中,以便针对增强的类进行拦截。

4. skywalking agent的插件定义

插件定义的类图如下,根据skywalking提供的插件开发指导,继承ClassInstanceMethodsEnhancePluginDefine、ClassStaticMethodsEnhancePluginDefine或ClassEnhancePluginDefine,并定义对应的拦截实现即可。

 

  • AbstractClassEnhancePluginDefine :所有Agent插件的基础抽象基类,侧重于定义,将增强的类转换为bytebuddy的DynamicType实例,并定义的enhance抽象方法

  • ClassEnhancePluginDefine :类增强插件定义抽象类,这个类控制所有增强操作,包括增强构造函数、实例方法和静态方法,并实现的enhance方法。

所有增强基于如下三种类型的拦截点。

ConstructorInterceptPoint

InstanceMethodsInterceptPoint

StaticMethodsInterceptPoint

  • ClassInstanceMethodsEnhancePluginDefine : 只需要增强类的实例方法,和直接继承ClassEnhancePluginDefine没有区别

  • ClassStaticMethodsEnhancePluginDefine : 只需要增强类的静态方法,和直接继承ClassEnhancePluginDefine没有区别

  • 具体的插件根据需要,实现如下方法:

public ClassMatch enhanceClass();

public ConstructorInterceptPoint[] getConstructorsInterceptPoints()

public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints()

public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints()

 

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐