Bootstrap

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

1. 插件

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

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

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

针对skywalking agent的插件开发指导,请参考:

1.1. 插件介绍

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

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

1.2. 插件机制

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

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

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

2. 源码解析环境

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

步骤如下:

-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的插件加载

插件加载的时序图如下:

具体插件加载流程如下:

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;
        }
public List getResources() {
        List cfgUrlPaths = new ArrayList();
        Enumeration 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;
    }

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

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

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

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

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()