Dubbo 2.7.3源码分析——JDK SPI篇

Posted Source Code Labs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dubbo 2.7.3源码分析——JDK SPI篇相关的知识,希望对你有一定的参考价值。


在开始分析Dubbo源码之前,我们需要先了解一下基础知识,这是Dubbo扩展机制的基石。


Dubbo 2.7.3源码分析——JDK SPI篇

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 文件的文件,

Dubbo 2.7.3源码分析——JDK SPI篇

该文件具体内容如下:

com.xxx.test.demo.Log4jcom.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(); } }}


输出如下:

Log4jLogback
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篇的主要内容,如果未能解决你的问题,请参考以下文章

dubbo源码分析01:SPI机制

dubbo源码解析-spi

dubbo源码解析-spi

源码解析 | Dubbo-SPI和IoC的前世今生

Dubbo SPI源码分析

dubbo源码分析之基于SPI的强大扩展