源码角度了解Skywalking之SPI在SKywalking中应用
源码角度了解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;
}
- 根据前缀和服务接口名加载配置文件,其中PREFIX的值为META-INF/services/,service.getName()的值为接口的全限定名,即com.xiepanpan.Log,这样就找到resources文件夹下我们定义的文件了
- 解析文件更新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();
}
- BootService是所有远程的接口,需要在插件机制开始工作时启动,而ServiceManager是用来管理BootService的,boot()方法中调用loadAllServices()方法加载所有的BootService,
- 依次遍历BootService集合调用对象的prepare()准备方法
- 依次遍历BootService集合调用对象的boot()启动方法
- 依次遍历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;
}
- 调用load()方法加载所有的BootService
- 遍历得到的所有BootService对象,针对 BootService 上的 @DefaultImplementor 和 @OverrideImplementor 注解进行处理,@DefaultImplementor表示是默认实现,@OverrideImplementor 注解可以覆盖默认实现,value值指定覆盖的默认实现。具体是没有这两个注解的加入到BootService集合中,有@DefaultImplementor标记的加入集合,有@OverrideImplementor标记的并且value指向的服务有@DefaultImplementor标记的对其覆盖,value指向的服务没有@DefaultImplementor注解就报错。
- 返回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机制非常好用和受欢迎,值得我们了解它的原理和使用。
- 点赞
- 收藏
- 关注作者
评论(0)