IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Dubbo SPI机制学习总结(持续更新...) -> 正文阅读

[Java知识库]Dubbo SPI机制学习总结(持续更新...)

参考文章:Dubbo的SPI机制分析

首先来看看 Java SPI 的机制

Java SPI 起初是提供给厂商做插件开发用的,例如数据库驱动java.sql.Driver,市面上各种各样的数据库,不同的数据库底层协议都不一样,为了方便开发者调用数据库而不用关心它们之间的差异,因此必须提供一个统一的接口来规范和约束这些数据库。有了统一的接口,数据库厂商就可以按照规范去开发自己的数据库驱动了。

厂商开发好数据库驱动了,应用如何使用呢?该使用哪个驱动呢?以 MySQL 为例,早期手写 JDBC 时,开发者需要手动注册驱动,现在已经不需要了,就是利用了 SPI 机制。

Java SPI 使用了策略模式,一个接口多种实现,开发者面向接口编程,具体的实现并不在程序中直接硬编码,而是通过外部文件进行配置。

Java SPI 约定了一个规范,使用步骤如下:

  • 编写一个接口。
  • 编写具体实现类。
  • 在 ClassPath 下的META-INF/services,目录创建以接口全限定名命名的文件,文件内容为实现类的全限定名,多个实现用换行符分割。
  • 通过 ServiceLoader 类获取具体实现。

在这里插入图片描述

接口:
在这里插入图片描述
实现类:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

简单实例

package org.sun.spi.services;

/**
 * 这是java 的 spi, 没有@SPI注解, 类似 mySQL
 */
public interface Say {
    void say();
}

实现类1

package org.sun.spi.impl;

import org.sun.spi.services.Say;

public class SayImpl implements Say {
    @Override
    public void say() {
        System.out.println("nihao .....");
    }
}

实现类2

package org.sun.spi.impl;

import org.sun.spi.services.Say;

public class SayWrapper implements Say {
    @Override
    public void say() {
        System.out.println("hello SayWrapper。。。。");
    }
}

META-INF/services 文件
在这里插入图片描述

测试:

public class Main {

    public static void main(String[] args) {
        ServiceLoader<Say> serviceLoader = ServiceLoader.load(Say.class);
        serviceLoader.forEach(say -> say.say());
        }

结果:

nihao .....
hello SayWrapper。。。。

Dubbo SPI机制

Dubbo SPI 定义了一套自己的规范,同时对 Java SPI 存在的问题进行了改进,优点如下:

  • 扩展类按需加载,节约资源。
  • SPI 文件采用 Key=Value 形式,可以根据扩展名灵活获取实现类。
  • 扩展类对象做了缓存,避免重复创建。
  • 扩展类加载失败有详细日志,方便排查。 支持 AOP 和 IOC。

Dubbo SPI 使用规范:

  • 编写接口,接口必须加@SPI 注解,代表它是一个可扩展的接口。
  • 编写实现类。
  • 在 ClassPath 下的META-INF/dubbo,目录创建以接口全限定名命名的文件,文件内容为 Key=Value 格式,Key 是扩展点的名称,Value 是扩展点实现类的全限定名。
  • 通过 ExtensionLoader 类获取扩展点实现。

Dubbo 默认会扫描META-INF/services
、META-INF/dubbo
、META-INF/dubbo/internal
三个目录下的配置,第一个是为了兼容 Java SPI,第三个是 Dubbo 内部使用的扩展点。

测试用例

// TODO

源码分析:

ExtensionLoader.getExtensionLoader

getExtension()

public T getExtension(String name, boolean wrap) {
        checkDestroyed();
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        // 如果 name 为true,则返回一个默认的扩展点
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        String cacheKey = name;
        if (!wrap) {
            cacheKey += "_origin";
        }
        // 创建或者返回一个holder对象, 用于缓存扩展类的实例
        final Holder<Object> holder = getOrCreateHolder(cacheKey);
        Object instance = holder.get();
        // 如果缓存不存在则创建一个实例
        if (instance == null) {
            // 同步设置,懒汉模式
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name, wrap);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;

PS : 有意思的类,学习

/**
 * 持有一个泛型
 * Helper Class for hold a value.
 */
public class Holder<T> {

    // 保证线程可见性
    private volatile T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }

}

上述代码就是先查缓存,如果未命中,则创建一个扩展对象,createExtension() 应该就是去指定的路径下查找name 对应的扩展点实现,并且实例化之后返回。

@SuppressWarnings("unchecked")
    private T createExtension(String name, boolean wrap) {
        // 根据 name 返回扩展类
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null || unacceptableExceptions.contains(name)) {
            throw findException(name);
        }
        try {
            // 从缓存中查找该类是否已经初始化
            // ConcurrentMap<Class<?>, Object> extensionInstances = new ConcurrentHashMap<>(64);
            T instance = (T) extensionInstances.get(clazz);
            if (instance == null) {
                // 如果没有,则重新创建一个实例并加入缓存, 反射机制
                extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
                instance = (T) extensionInstances.get(clazz);
                // 初始化前的相关操作
                instance = postProcessBeforeInitialization(instance, name);
                // 依赖注入
                injectExtension(instance);
                instance = postProcessAfterInitialization(instance, name);
            }

            if (wrap) {
                // 通过 Wrapper 进行包装
                List<Class<?>> wrapperClassesList = new ArrayList<>();
                if (cachedWrapperClasses != null) {
                    wrapperClassesList.addAll(cachedWrapperClasses);
                    wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                    Collections.reverse(wrapperClassesList);
                }

                if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                    for (Class<?> wrapperClass : wrapperClassesList) {
                        Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                        boolean match = (wrapper == null) ||
                            ((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
                                !ArrayUtils.contains(wrapper.mismatches(), name));
                        if (match) {
                            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                            instance = postProcessAfterInitialization(instance, name);
                        }
                    }
                }
            }

            // Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

继续跟踪getExtensionClasses

  • 从缓存中获取已经被加载的扩展类
  • 如果未命中缓存, 则调用loadExtensionClasses 加载扩展类
 private Map<String, Class<?>> getExtensionClasses() {
        // Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;

loadExtensionClasses()

/**
     * synchronized in getExtensionClasses
     */
    private Map<String, Class<?>> loadExtensionClasses() {
        checkDestroyed();
        // 获得当前type扩展接口的默认扩展对象, 并且缓存
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();

        for (LoadingStrategy strategy : strategies) {
        // 加载指定文件目录下的配置文件
            loadDirectory(extensionClasses, strategy, type.getName());

            // compatible with old ExtensionFactory
            if (this.type == ExtensionInjector.class) {
                loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
            }
        }

        return extensionClasses;
    }

(精彩后续,请看下回分解)

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-11 21:59:56  更:2022-03-11 22:02:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:48:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码