美文网首页
JDK SPI与Dubbo SPI解析

JDK SPI与Dubbo SPI解析

作者: DH大黄 | 来源:发表于2021-05-16 16:54 被阅读0次

Dubbo为了达到OCP原则(对拓展开放,对修改封闭原则),采用了微内核+插件的架构

微内核架构也被称为插件化架构(Plug-in Architecture),这是一种面向功能进行拆分的可扩展性架构。内核功能是比较稳定的,只负责管理插件的生命周期,不会因为系统功能的扩展而不断进行修改。功能上的扩展全部封装到插件之中,插件模块是独立存在的模块,包含特定的功能,能拓展内核系统的功能

内核可以采用Factory,IOC,OSGi等方式管理插件生命周期,Dubbo最终采用SPI机制来加载插件

参考JDK SPI并进行了性能优化以及功能增强

JDK SPI

什么是SPI

使用 Java 语言访问数据库时我们会使用到 java.sql.Driver 接口,不同数据库产品底层的协议不同,提供的 java.sql.Driver 实现也不同,在开发 java.sql.Driver 接口时,开发人员并不清楚用户最终会使用哪个数据库,在这种情况下就可以使用 Java SPI 机制在实际运行过程中,为 java.sql.Driver 接口寻找具体的实现。

JDK SPI机制

当服务的提供者提供了一种接口的实现之后,需要在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,此文件记录了该 jar 包提供的服务接口的具体实现类。当某个应用引入了该 jar 包且需要使用该服务时,JDK SPI 机制就可以通过查找这个 jar 包的 META-INF/services/ 中的配置文件来获得具体的实现类名,进行实现类的加载和实例化,最终使用该实现类完成业务功能。

源码分析

JDK SPI 入口方法 ServiceLoader.load()

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 获取当前线程的classloader,假如classLoader为空则使用SystemClassLoader
    // loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}</pre>

然后调用reload

// Cached providers, in instantiation order
// 缓存,用来缓存 ServiceLoader 对象
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
public void reload() {
    // 清空缓存
    providers.clear();
    // 迭代器
    lookupIterator = new LazyIterator(service, loader);
}</pre>
private static final String PREFIX = "META-INF/services/";
private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
            // 加载META-INF/services/下的配置文件
            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);
        }
    }
    // 按行SPI遍历配置文件的内容
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        // 解析配置文件
        pending = parse(service, configs.nextElement());
    }
    // 更新 nextName 字段
    nextName = pending.next();
    return true;
}

private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    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
}
public Iterator<S> iterator() {
    return new Iterator<S>() {

        // 用来迭代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();
        }

    };
}

流程解析

image

应用场景

例如 JDBC 的应用,在 mysql-connector-java-*.jar 包中的 META-INF/services 目录下,有一个 java.sql.Driver 文件中只有一行内容

<pre data-language="java" id="XG0vJ" style="border: 1px solid #e8e8e8; border-radius: 2px; background: #f9f9f9; padding: 16px; font-size: 13px; color: #595959">com.mysql.cj.jdbc.Driver </pre>

Dubbo SPI

借鉴JDK SPI思想,实现了自身的一套SPI机制

Dubbo 按照 SPI 配置文件的用途,将其分成了三类目录。

  • META-INF/services/ 目录:该目录下的 SPI 配置文件用来兼容 JDK SPI 。

  • META-INF/dubbo/ 目录:该目录用于存放用户自定义 SPI 配置文件。

  • META-INF/dubbo/internal/ 目录:该目录用于存放 Dubbo 内部使用的 SPI 配置文件。

同时SPI配置文件改为KV格式,保证了只需要根据需要加载其中一个实现类

Dubbo SPI核心实现

  • @SPI注解

  • @Adaptive 注解与适配器

  • 自动包装特性

  • 自动装配特性

  • @Activate 注解与自动激活特性

如果Dubbo中的某个接口被 @SPI 注解修饰时,就表示该接口是扩展接口,例如 org.apache.dubbo.rpc.Protocol 接口

@SPI("dubbo")
public interface Protocol {
    // ....   
}
// dubbo-rpc/dubbo-rpc-dubbo/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol</pre>

<pre data-language="java" id="yUNuY" style="border: 1px solid #e8e8e8; border-radius: 2px; background: #f9f9f9; padding: 16px; font-size: 13px; color: #595959">// org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
public static final String NAME = "dubbo";
public static DubboProtocol getDubboProtocol() {
     if (INSTANCE == null) {
         // ExtensionLoader 处理 @SPI 注解
         // ExtensionLoader.getExtensionLoader() 方法会根据扩展接口从 EXTENSION_LOADERS 缓存中查找相应的 ExtensionLoader 实例(实现见下)
  ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(DubboProtocol.NAME);
     }

     return INSTANCE;
 }

ExtensionLoader 三个核心字段

// LoadingStrategy 接口有三个实现(通过 JDK SPI 方式加载的)
// 分别对应前面介绍的三个 Dubbo SPI 配置文件所在的目录,且都继承了 Prioritized 这个优先级接口,默认优先级是  DubboInternalLoadingStrategy > DubboLoadingStrategy > ServicesLoadingStrateg
private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
/**
 * Dubbo 中一个扩展接口对应一个 ExtensionLoader 实例,该集合缓存了全部 ExtensionLoader 实例,
 * 其中的 Key 为扩展接口,Value 为加载其扩展实现的 ExtensionLoader 实例
 */
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
/**
 * 该集合缓存了扩展实现类与其实例对象的映射关系。
 * 即扩展点配置文件里面的kv
 * 在前文示例中,Key 为 Class,Value 为 DubboProtocol 对象
 */
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);

getExtensionLoader 实现原理

/**
 * 会根据扩展接口从 EXTENSION_LOADERS 缓存中查找相应的 ExtensionLoader 实例
 */
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    }
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type +
                                           ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }

    // 获取对应的某个类的类加载器
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        // 不存在,则放一个新的进去
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

getExtension 实现原理

public T getExtension(String name, boolean wrap) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    // getOrCreateHolder()方法中封装了查找cachedInstances缓存的逻辑
    final Holder<Object> holder = getOrCreateHolder(name);
    Object instance = holder.get();
    if (instance == null) {
        // double-check防止并发问题
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // 根据扩展名从SPI配置文件中查找对应的扩展实现类
                // 在 createExtension() 方法中完成了 SPI 配置文件的查找以及相应扩展实现类的实例化,同时还实现了自动装配以及自动 Wrapper 包装等功能
                instance = createExtension(name, wrap);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

createExtension 实现原理

@SuppressWarnings("unchecked")
private T createExtension(String name, boolean wrap) {
    // 获取 cachedClasses 缓存,根据扩展名从 cachedClasses 缓存中获取扩展实现类。
    // 如果 cachedClasses 未初始化,则会扫描前面介绍的三个 SPI 目录获取查找相应的 SPI 配置文件,
    // 然后加载其中的扩展实现类,最后将扩展名和扩展实现类的映射关系记录到 cachedClasses 缓存中。
    // 这部分逻辑在 loadExtensionClasses() 和 loadDirectory() 方法中。
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        // 根据扩展实现类从 EXTENSION_INSTANCES 缓存中查找相应的实例。如果查找失败,会通过反射创建扩展实现对象。
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 自动装配扩展实现对象中的属性(即调用其 setter)。这里涉及 ExtensionFactory 以及自动装配的相关内容,
        injectExtension(instance);

        if (wrap) {

            List<Class<?>> wrapperClassesList = new ArrayList<>();
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
                wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                Collections.reverse(wrapperClassesList);
            }

            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                // 自动包装扩展实现对象。这里涉及 Wrapper 类以及自动包装特性的相关内容,本课时后面会进行详细介绍。
                for (Class<?> wrapperClass : wrapperClassesList) {
                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                    if (wrapper == null
                        || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                    }
                }
            }
        }

        // 如果扩展实现类实现了 Lifecycle 接口,在 initExtension() 方法中会调用 initialize() 方法进行初始化
        initExtension(instance);
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                                        type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

以上是Dubbo如何通过@SPI注解找到对应的实现类,接下来讲解下@Adaptive注解的适配器实现

在ExtensionLoader.createExtension()方法中,扫描SPI配置文件时,会调用loadClass()方法加载SPI配置文件中指定的类

classes = loadExtensionClasses();

加载class流程

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                       boolean overridden) throws NoSuchMethodException {
    if (!type.isAssignableFrom(clazz)) {
        throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                                        type + ", class line: " + clazz.getName() + "), class "
                                        + clazz.getName() + " is not subtype of interface.");
    }
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        // 处理@Adaptive注解
        cacheAdaptiveClass(clazz, overridden);
    } else if (isWrapperClass(clazz)) {
        // 处理Wrapper类
        cacheWrapperClass(clazz);
    } else {
        // 处理真正的扩展实现类
        // 扩展实现类必须有无参构造函数
        clazz.getConstructor();
        // 兜底:SPI配置文件中未指定扩展名称,则用类的简单名称作为扩展名
        if (StringUtils.isEmpty(name)) {
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
            }
        }

        String[] names = NAME_SEPARATOR.split(name);
        if (ArrayUtils.isNotEmpty(names)) {
            // 将包含@Activate注解的实现类缓存到cachedActivates集合中
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                // 在cachedNames集合中缓存实现类->扩展名的映射
                cacheName(clazz, n);
                // 在cachedClasses集合中缓存扩展名->实现类的映射
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}

流程解析

image

名词定义

扩展点:通过 SPI 机制查找并加载实现的接口(又称“扩展接口”)。前文示例中介绍的 Log 接口、com.mysql.cj.jdbc.Driver 接口,都是扩展点。

扩展点实现:实现了扩展接口的实现类。

SPI与策略模式的比较

如果从代码接入的级别来看,策略模式还是在原有项目中进行代码修改,只不过它不会修改原有类中的代码,而是新建了一个类。而 SPI 机制则是不会修改原有项目中的代码,其会新建一个项目,最终以 Jar 包引入的方式代码。
从这一点来看,无论策略模式还是 SPI 机制,他们都是将修改与原来的代码隔离开来,从而避免新增代码对原有代码的影响。但策略模式是类层次上的隔离,而 SPI 机制则是项目框架级别的隔离。
从应用领域来说,策略模式更多应用在业务领域,即业务代码书写以及业务代码重构。而 SPI 机制更多则是用于框架的设计领域,通过 SPI 机制提供的灵活性,让框架拥有良好的插件特性,便于扩展。
总结一下,策略模式与 SPI 机制有下面几点异同:
• 从设计思想来看。策略模式和 SPI 机制其思想是类似的,都是通过一定的设计隔离变化的部分,从而让原有部分更加稳定。
• 从隔离级别来看。策略模式的隔离是类级别的隔离,而 SPI 机制是项目级别的隔离。
• 从应用领域来看。策略模式更多用在业务代码书写,SPI 机制更多用于框架的设计。

相关文章

  • 【dubbo源码】6.dubbo的spi机制

    Dubbo的SPI由JDK标准的SPI加强而来 Dubbo 改进了 JDK 标准的 SPI 的以下问题: JDK ...

  • 1.0 dubbo源码解析之@SPI

    @SPI是了解dubbo源码的基础,dubbo-spi是通过jdk-spi优化而来的,对于jdk-spi的原理这里...

  • JDK SPI与Dubbo SPI解析

    Dubbo为了达到OCP原则(对拓展开放,对修改封闭原则),采用了微内核+插件的架构 微内核架构也被称为插件化架构...

  • DUBBO SPI实现核心-ExtensionLoader

    概念 dubbo spi是dubbo对JDK spi的升级,针对JDK spi的一些弱势进行了优化,官网的介绍如下...

  • 13.Dubbo增强的SPI

    Dubbo的SPI是在JDK提供的标准SPI上进行增强的,至于JDK的SPI有兴趣可以百度看下。 Dubbo的SP...

  • Dubbo SPI 机制

    前提 Dubbo的SPI是从JDK的SPI 扩展加强而来的。 JDK中SPI 机制如下(可以查看JDBC的实现) ...

  • jdk SPI与dubbo SPI

    1.jdk SPI介绍 SPI 全称为 Service Provider Interface,是一种服务发现机制。...

  • spi 机制

    JDK 的 SPI 机制1、META-INF/services/org.speed.dubbo.spi.servi...

  • 第2章 JDK SPI 的设计与实现

    SPI 机制是实现可扩展性的一种方式。Dubbo SPI 是从 JDK SPI(Service Provider ...

  • JAVA SPI解析

    JAVA SPI解析 在阅读Dubbo源码时发现Dubbo针对java的spi机制做了扩展。那么spi究竟是什么呢...

网友评论

      本文标题:JDK SPI与Dubbo SPI解析

      本文链接:https://www.haomeiwen.com/subject/mlrrjltx.html