目录
前言
SPI是什么?解决了什么问题?Dubbo 为什么要实现SPI?Java SPI有什么问题?
看完本文您应该对此有个大致了解,并且Dubbo SPI作为Dubbo核心机制,了解Dubbo SPI的使用与实现原理,对我们深入理解Dubbo的整体设计思想会很大的帮助。
一、Dubbo SPI&Java SPI的脑图
先上图,方便大家快速定位所需内容(如果脑图实用,欢迎点赞留言)。

二、Java SPI
1. 代码应用
- 定义接口类:
public interface JavaSPIInterface { String echo(String msg); } - 定义实现类:
// 以下3个实现类应该分为三个Java文件,为了方便我放在了一个代码段中 // 实现类1 public class JavaSPIInterfaceImpl1 implements JavaSPIInterface { @Override public String echo(String msg) { return "JavaSPIInterfaceImpl1 -- " + msg; } } // 实现类2 public class JavaSPIInterfaceImpl2 implements JavaSPIInterface { @Override public String echo(String msg) { return "JavaSPIInterfaceImpl2 -- " + msg; } } // 实现类3 public class JavaSPIInterfaceImpl3 implements JavaSPIInterface { @Override public String echo(String msg) { return "JavaSPIInterfaceImpl3 -- " + msg; } } - 配置扩展点,配置文件存META-INF/services/com.xxx.JavaSPIInterface(即接口的全路径):
# 扩展点接口实现的全路径 com.xxx.impl.JavaSPIInterfaceImpl1 com.xxx.impl.JavaSPIInterfaceImpl2 com.xxx.impl.JavaSPIInterfaceImpl3 -
单元测试类
ServiceLoader<JavaSPIInterface> javaSPIInterfaces = ServiceLoader.load(JavaSPIInterface.class); Iterator<JavaSPIInterface> iterator = javaSPIInterfaces.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next().echo("sks-9527")); }如上的4个步骤就是Java SPI的核心流程。
2.源码分析
- ServiceLoader.load(JavaSPIInterface.class);源码解析:
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); } ..... private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; // 核心方法,重新加载扩展点配置 reload(); } ..... public void reload() { providers.clear(); //最终是创建了一个LazyIterator 实例,此时【并没有】发生扩展点文件的加载以及扩展点实现类的实例化 lookupIterator = new LazyIterator(service, loader); } ..... private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; } -
LazyIterator#hasNext源码解析:
public Iterator<S> iterator() { return new Iterator<S>() { // 获取SerivceLoader属性LinkedHashMap<String,S> providers的迭代器, //LinkedHashMap<String,S> providers用于存储扩展点的实例(key为实现类的全路径,value为对应实例) Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); public boolean hasNext() { // knownProviders.hasNext() 为true,表示缓存存在数据,不需要lookupIterator进行再次加载。 // 为false,则需要加载扩展点配置来判断是否存在下一个实例。 if (knownProviders.hasNext()) return true; // 加载扩展点配置文件 return lookupIterator.hasNext(); } public S next() { // 缓存实例存在数据,则直接返回,避免重复实例化扩展点实现 if (knownProviders.hasNext()) return knownProviders.next().getValue(); // 加载扩展点实现 return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; } ....... // lookupIterator.hasNext(); 最终调用如下代码 private boolean hasNextService() { if (nextName != null) { return true; } // Enumeration<URL> configs 扩展点的文件资源, // 为null时则进行加载,不为null,则进行获取扩展点文件的内容 // 很明显——【线程不安全的操作】 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); } } // pending 扩展点配置类全路径字符串的迭代器 while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } // 解析配置文件内容 pending = parse(service, configs.nextElement()); } // 获取扩展点实现类的下一个全路径字符串, // 在lookupIterator#next中会根据nextName进行扩展点实现类的实例化 nextName = pending.next(); return true; } ..... // 加载扩展点配置文件内容 private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError { InputStream in = null; BufferedReader r = null; // 存储扩展点配置文件每一行(扩展点实现类的全路径)的字符串 ArrayList<String> names = new ArrayList<>(); try { in = u.openStream(); r = new BufferedReader(new InputStreamReader(in, "utf-8")); int lc = 1; while ((lc = parseLine(service, u, r, lc, names)) >= 0); } catch (IOException x) {

本文详细探讨了Dubbo SPI和Java SPI的区别,从代码应用、源码分析到问题分析,揭示了Dubbo SPI作为核心机制的重要性。内容包括Java SPI的资源消耗、灵活性不足和线程不安全问题,以及Dubbo SPI的扩展点加载、自适应编译和IoC与AOP原理。通过深入理解,有助于提升对Dubbo设计思想的认识。
最低0.47元/天 解锁文章
700

被折叠的 条评论
为什么被折叠?



