深入浅出Dubbo——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 文件的文件,

640?wx_fmt=png

该文件具体内容如下:

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()方法,如图所示:

 

640?wx_fmt=png

首先来看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实现,但是思想类似,在下一篇再详细介绍其设计和代码实现,洗洗睡了!

640?wx_fmt=png

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值