Dubbo 2.7.3源码分析——JDK SPI篇
Posted Source Code Labs
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dubbo 2.7.3源码分析——JDK SPI篇相关的知识,希望对你有一定的参考价值。
在开始分析Dubbo源码之前,我们需要先了解一下基础知识,这是Dubbo扩展机制的基石。
JDK SPI 示例
SPI 的全名为 Service Provider Interface,我们常说“面向接口编程”,为了在模块装配的时候不在程序里指明是哪个实现,就需要动态的查找接口实现。
JDK的SPI机制可以帮助我们查找某个接口的实现类。java.util.ServiceLoader 会去加载 META-INF/service/ 目录下的配置文件。
废话不多说,直接上Demo吧
先来一个Log接口以及两个实现类——Log4j和Logback,如下所示:
public interface Log {
void exec();
}
public class Log4j implements Log {
@Override
public void exec() {
System.out.println("Log4j");
}
}
public class Logback implements Log {
@Override
public void exec() {
System.out.println("Logback");
}
}
在项目的resources/META-INF/services目录下添加一个名为 com.xxx.test.demo.Log 文件的文件,
该文件具体内容如下:
com.xxx.test.demo.Log4j
com.xxx.test.demo.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.exec();
}
}
}
输出如下:
Log4j
Logback
Process finished with exit code 0
JDK SPI源码分析
JDK SPI的使用方式非常简单是不是与Spring的依赖注入有点类似,是不是很意外呢?
下面深入ServiceLoader.load()方法的具体实现:
1、获取当前ClassLoader:
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取当前ClassLoader
ClassLoader cl = Thread.currentThread()
.getContextClassLoader();
return ServiceLoader.load(service, cl);
}
2、在ServiceLoader.load()的最底层会调用ServiceLoader.reload()方法,其中清理缓存并创建LazyIterator,LazyIterator是ServiceLoader的内部类:
// 缓存,用来缓存ServiceLoader加载上来的实例
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
public void reload() {
providers.clear(); // 清空缓存
lookupIterator = new LazyIterator(service, loader);
}
我们在Main方法中使用的迭代器底层就是调用了ServiceLoader.LazyIterator实现的。LazyIterator.next()方法最终调用的是nextService()方法,hasNext()方法最终调用的是hasNextService()方法,如图所示:
首先来看LazyIterator.hasNextService()方法主要负责查找META-INF下的那个配置文件,并进行遍历,大致实现如下所示:
private static final String PREFIX = "META-INF/services/";
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
// 拼起来就是META-INF目录下定义的那个配置文件
String fullName = PREFIX + service.getName();
// 加载配置文件
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 解析配置文件
pending = parse(service, configs.nextElement());
}
nextName = pending.next(); // 更新nextName
return true;
}
再来看LazyIterator.nextService()方法,它负责实例化指定的实现类,并将实例化的对象放到缓存中,大致具体实现如下所示:
private S nextService() {
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 加载nextName指定的类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service, "Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) { // 检测类型
fail(service, "Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance()); // 创建实例
providers.put(cn, p); // 将实例添加到缓存
return p;
} catch (Throwable x) {
fail(service, "Provider " + cn + " could not be instantiated", x);
}
throw new Error(); // This cannot happen
}
最后再来看一下ServiceLoader.Iterator()方法返回的真正迭代器:
public Iterator<S> iterator() {
return new Iterator<S>() {
// knownProviders用来迭代providers缓存
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
// 先走缓存,缓存没有走LazyIterator
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
// 先走缓存,缓存没有走LazyIterator
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
Dubbo SPI引子
通过前面的介绍可以发现,JDK SPI查找具体实现的时候,需要遍历所有的实现并实例化,然后在Main方法的循环中才能找到我们需要实现。这就很坑了,需要把所有的实现都实例化了。
Dubbo为了解决这个问题,自己搞了一套SPI实现,但是思想类似,在下一篇再详细介绍其设计和代码实现,洗洗睡了!
以上是关于Dubbo 2.7.3源码分析——JDK SPI篇的主要内容,如果未能解决你的问题,请参考以下文章