(四)Dubbo的SPI机制的底层是如何实现的?

(一)什么是SPI机制?Java中的SPI机制是如何实现的?

(1)首先先说一下JavaSPI机制(Service Provider Interface)其实说白了就是定义一个接口,但是可以有多个实现该接口的实现类,其实也是一种服务发现机制。

  • 其实SPI机制的本质就是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类

(2)那么Dubbo中的SPI机制和Java中的SPi机制又有什么区别呢?

  • 首先Java中的SPI机制是基于接口的编程+策略模式+配置文件的组合方式实现的动态加载机制

  • 其实具体的JavaSPI的实现就是:

    • 1.你先定义一个A接口,比如是com.wangwei.A这个接口
    • 2.然后你需要在src/main/resources/ 下建立 /META-INF/services 目录下定义和A接口相同的文件com.wangwei.A名字,然后需要在这个com.wangwei.A文件中 定义一些你需要实现这个接口的实现类这里有多个实现类的
    • 3.然后你在使用的时候需要去遍历这个接口的所有实现类,然后拿到你想要拿到的实现类,然后使用,是这样的一个流程。

在这里插入图片描述
那么其实JavaSPI机制的底层实现原理:

其实主要ServiceLoader这个类进行去加载的


public final class ServiceLoader<S>
    implements Iterable<S>
{

    private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    private final Class<S> service;

    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;

这里中间就直接省略了。。。。。。

  private class LazyIterator  implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            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);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

JavaSP机制的缺点:

  • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
  • 多个并发多线程使用ServiceLoader类的实例是不安全的。
  • 扩展如果依赖其他的扩展,做不到自动注入和装配
  • 不提供类似于Spring的IOC和AOP功能
  • 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持

(二)dubbo的SPI机制底层是如何实现的?

(1)dubbo中的SPI机制

  • 其实为什么在dubbo中又重新定义了一套dubbo的SPI机制,就是因为java中的SPI机制的一些缺点,不能满足dubbo的使用场景或者说效率没有那么高效
  • 所以dubbo自己封装了一套更强大的SPI机制,直接封装到了ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径,并且可以直接给实现类进行一个命名,然后在使用的时候,直接通过@SPI(“名字”)去找到具体的实现类,比如:random(名字)=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance(实现类)

(2)dubbo中SPI机制的使用流程:

  1. 首先如果你想给一个接口需要用dubbo的SPI机制实现,那么首先你需要给这个接口上加上@SPI(“使用默认的实现类的名字”)这个注解
  2. 然后需要在你接口中的方法上加上一个@Adaptive注解,这个注解的核心功能就是:会为该方法生成对应的代码。方法内部会根据方法的参数,来决定使用哪个扩展,@Adaptive注解用在类上代表实现一个装饰类,类似于设计模式中的装饰模式
  3. 然后通过你在配置文件中配置好并实现你需要的实现类,然后供其进行调用完成,类似下面
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
leastactive=com.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
consistenthash=com.alibaba.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance

(3)dubbo是如何实现的这个增强版的SPI机制的?源码级别

  1. 首先dubbo其实是通过 ExtensionLoader 的 getExtensionLoader 方法获取一个ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象
  2. getExtension方法中的核心就是创建一个扩展对象
public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    if ("true".equals(name)) {
        // 获取默认的拓展实现类
        return getDefaultExtension();
    }
    // Holder,顾名思义,用于持有目标对象
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    Object instance = holder.get();
    // 双重检查
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // 创建拓展实例
                instance = createExtension(name);
                // 设置实例到 holder 中
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}
  1. 然后到createExtension()这个方法中主要的流程是:
    • 通过 getExtensionClasses 获取所有的拓展类
    • 通过反射创建拓展对象
    • 向拓展对象中注入依赖(这里使用的是 Dubbo IOC 与 AOP 的具体实现)
    • 将拓展对象包裹在相应的 Wrapper 对象中(这里使用的是 Dubbo IOC 与 AOP 的具体实现)
private T createExtension(String name) {
    // 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            // 通过反射创建实例
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 向实例中注入依赖
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
            // 循环创建 Wrapper 实例
            for (Class<?> wrapperClass : wrapperClasses) {
                // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
                // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
                instance = injectExtension(
                    (T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("...");
    }
}
  1. 然后是getExtensionClasses()方法去加载,如果 classes 仍为 null,则通过 loadExtensionClasses 加载拓展类
private Map<String, Class<?>> getExtensionClasses() {
    // 从缓存中获取已加载的拓展类
    Map<String, Class<?>> classes = cachedClasses.get();
    // 双重检查
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 加载拓展类
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}
  1. 然后是loadExtensionClasses()方法,主要做了两件事:一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件
private Map<String, Class<?>> loadExtensionClasses() {
    // 获取 SPI 注解,这里的 type 变量是在调用 getExtensionLoader 方法时传入的
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation != null) {
        String value = defaultAnnotation.value();
        if ((value = value.trim()).length() > 0) {
            // 对 SPI 注解内容进行切分
            String[] names = NAME_SEPARATOR.split(value);
            // 检测 SPI 注解内容是否合法,不合法则抛出异常
            if (names.length > 1) {
                throw new IllegalStateException("more than 1 default extension name on extension...");
            }

            // 设置默认名称,参考 getDefaultExtension 方法
            if (names.length == 1) {
                cachedDefaultName = names[0];
            }
        }
    }

    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    // 加载指定文件夹下的配置文件
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadDirectory(extensionClasses, DUBBO_DIRECTORY);
    loadDirectory(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}

总结dubbo的SPI机制的特点:(符合Dubbo的微内核+插件的总体设计思想)

首先dubbo的核心都是基于Dubbo的微内核+插件的机制,而SPI机制更是dubbo中最核心之一的地方,他的设计符合开闭原则,有很好的扩展性。

  • 对Dubbo进行扩展,不需要改动Dubbo的源码,自定义的Dubbo的扩展点实现,是一个普通的Java类,Dubbo没有引入任何Dubbo特有的元素,对代码侵入性几乎为零。
  • 将扩展注册到Dubbo中,只需要在ClassPath中添加配置文件。使用简单。而且不会对现有代码造成影响。符合开闭原则
  • Dubbo的扩展机制支持IoC,AoP等高级功能
  • Dubbo的扩展机制能很好的支持第三方IoC容器,默认支持Spring Bean,可自己扩展来支持其他容器,比如Google的Guice
  • 切换扩展点的实现,只需要在配置文件中修改具体的实现,不需要改代码。使用方便

(三)dubbo中的SPI机制和Java中的SPI机制有什么区别?

看完上面的讲解,想必你自己肯定也能猜到dubbo的SPI机制和Java中的SPI机制有什么区别,不然我这篇博客不是白写了吗,哭唧唧。。。。欢迎探讨

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章