源码角度了解Skywalking之SPI在SKywalking中应用

举报
周杰伦本人 发表于 2022/10/30 17:58:11 2022/10/30
【摘要】 源码角度了解Skywalking之SPI在SKywalking中应用上篇文章中我们说到SKywalking的启动流程是怎样的,其中有一步是利用JDK的SPI机制来启动插件服务,今天我们就看一下具体是怎么利用JDK的SPI机制的。 JDK 的SPI所谓SPI就是Service Provider Interface,我们通过日志的接口实现类来演示一下SPI的使用第一步定义接口public in...

源码角度了解Skywalking之SPI在SKywalking中应用

上篇文章中我们说到SKywalking的启动流程是怎样的,其中有一步是利用JDK的SPI机制来启动插件服务,今天我们就看一下具体是怎么利用JDK的SPI机制的。

JDK 的SPI

所谓SPI就是Service Provider Interface,我们通过日志的接口实现类来演示一下SPI的使用

第一步定义接口

public interface Log {
    void log(String info);
}

定义实现接口的两个实现类:

public class Log4j implements Log {
    @Override
    public void log(String info) {
        System.out.println("Log4j:" + info);
    }
}
public class Logback implements Log {
    @Override
    public void log(String info) {
        System.out.println("Logback:" + info);
    }
}

第二步:在resources的META-INF/services下定义文件,文件名就是接口的全限定名com.xiepanpan.Log,文件的内容就是对应的实现类的全限定名

com.xiepanpan.impl.Log4j
com.xiepanpan.impl.Logback

第三步:使用接口实现类

我们看一下main()方法:

public class Main {
    public static void main(String[] args) {
        ServiceLoader<Log> serviceLoader =ServiceLoader.load(Log.class);
        Iterator<Log> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            Log log = iterator.next();
            log.log("JDK SPI");
        }
    }
}

这段代码很好理解,通过ServiceLoader的load()方法加载Log接口的实现类,遍历实现类,依次调用实现类的log()方法,输出结果:

Log4j:JDK SPI
Logback:JDK SPI

有些同学就会产生疑问❓,为什么使用ServiceLoader.load(Log.class)就能加载到对应的实现类呢?

我们看一下这个方法具体做了什么,

ServiceLoader.load()

进入ServiceLoader.load()方法,最终调用的是ServiceLoader的reload()方法,方法中 创建了LazyIterator 迭代器对象

serviceLoader.iterator()

serviceLoader.iterator()中创建了Iterator对象,这个类的hasNext()方法和next()方法中都是先从缓存中查询如果有返回,如果没有就调用LazyIterator的hasNext()方法和next()方法

LazyIterator类

我们看一下LazyIterator类的hasNext()方法和next()方法的实现

LazyIterator的hasNext()方法

hasNext()方法会调用hasNextService()方法:

private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
  1. 根据前缀和服务接口名加载配置文件,其中PREFIX的值为META-INF/services/,service.getName()的值为接口的全限定名,即com.xiepanpan.Log,这样就找到resources文件夹下我们定义的文件了
  2. 解析文件更新nextName,并返回true

LazyIterator的next()方法

next()方法会调用nextService()方法:

这个方法主要是通过Class.forName()方法来实例化nextName指向的类

现在我们知道为什么可以通过文件来配置我们需要使用的类了。

SPI在Skywalking中的应用

ServiceManager.INSTANCE.boot()方法

Skywalking启动初始化的时候调用ServiceManager.INSTANCE.boot()方法,我们看一下这个方法:

public void boot() {
        bootedServices = loadAllServices();

        prepare();
        startup();
        onComplete();
    }
   
  1. BootService是所有远程的接口,需要在插件机制开始工作时启动,而ServiceManager是用来管理BootService的,boot()方法中调用loadAllServices()方法加载所有的BootService,
  2. 依次遍历BootService集合调用对象的prepare()准备方法
  3. 依次遍历BootService集合调用对象的boot()启动方法
  4. 依次遍历BootService集合调用对象的onComplete()完成方法

ServiceManager的loadAllServices()方法

ServiceManager的loadAllServices()方法:

private Map<Class, BootService> loadAllServices() {
        Map<Class, BootService> bootedServices = new LinkedHashMap<Class, BootService>();
        List<BootService> allServices = new LinkedList<BootService>();
        load(allServices);
        Iterator<BootService> serviceIterator = allServices.iterator();
        while (serviceIterator.hasNext()) {
            。。。。。
        }
        return bootedServices;
    }
  1. 调用load()方法加载所有的BootService
  2. 遍历得到的所有BootService对象,针对 BootService 上的 @DefaultImplementor 和 @OverrideImplementor 注解进行处理,@DefaultImplementor表示是默认实现,@OverrideImplementor 注解可以覆盖默认实现,value值指定覆盖的默认实现。具体是没有这两个注解的加入到BootService集合中,有@DefaultImplementor标记的加入集合,有@OverrideImplementor标记的并且value指向的服务有@DefaultImplementor标记的对其覆盖,value指向的服务没有@DefaultImplementor注解就报错。
  3. 返回BootService集合

ServiceManager的load()方法

ServiceManager的load()方法:

void load(List<BootService> allServices) {
        Iterator<BootService> iterator = ServiceLoader.load(BootService.class, AgentClassLoader.getDefault()).iterator();
        while (iterator.hasNext()) {
            allServices.add(iterator.next());
        }
    }

看看是不是似曾相识,和我们写的日志加载的demo非常相像,也是利用ServiceLoader的load()方法来加载,这里自然是加载resources/META-INF/services/org.apache.skywalking.apm.agent.core.boot.BootService文件了,在apm-agent-core模块中有定义这个文件,文件中都是BootService接口的实现类

总结

这篇文章我们讲了什么JDK的SPI以及它的原理,同时介绍了Skywalking在加载BootService接口的实现类的时候是怎么利用SPI机制的。JDK的SPI机制应用很广泛,在jdbc驱动加载和dubbo等服务中也有使用,可见JDK的SPI机制非常好用和受欢迎,值得我们了解它的原理和使用。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。