我是靠谱客的博主 明亮冰棍,最近开发中收集的这篇文章主要介绍Dubbo中的SPI机制Dubbo中的SPI机制概述Java原生的SPIDubbo的SPI仿造源码自定义实现Dubbo SPI机制,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Dubbo中的SPI机制

概述

Service Provider Interface 即 SPI,是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件。可以让不同的厂商针对统一接口编写不同的实现

SPI实际上是“接口+策略模式+配置文件”实现的动态加载机制。在系统设计中,模块之间通常基于接口编程,不直接显示指定实现类。一旦代码里指定了实现类,就无法在不修改代码的情况下替换为另一种实现。为了达到动态可插拔的效果,java提供了SPI以实现服务发现。

SPI机制的应用场景有很多,我们比较常用的就是JDBC,Dubbo等

在谈Dubbo的SPI机制前我们需要先了解一下Java原生的SPI机制

Java原生的SPI

  1. 首先我,我们先创建一个接口,接口中定义一个方法

package cuit.epoch.pymjl.spi;

/**
 * @author Pymjl
 * @version 1.0
 * @date 2022/5/2 17:43
 **/
public interface Animal {
    /**
     * 动物叫
     */
    void call();
}

  1. 然后我们分别写两个不同的实现类,实现这个接口

package cuit.epoch.pymjl.spi.impl;

import cuit.epoch.pymjl.spi.Animal;

/**
 * @author Pymjl
 * @version 1.0
 * @date 2022/5/2 17:44
 **/
public class Cat implements Animal {
    @Override
    public void call() {
        System.out.println("猫叫:喵喵喵~");
    }
}

package cuit.epoch.pymjl.spi.impl;

import cuit.epoch.pymjl.spi.Animal;

/**
 * @author Pymjl
 * @version 1.0
 * @date 2022/5/2 17:45
 **/
public class Dog implements Animal {
    @Override
    public void call() {
        System.out.println("狗叫:汪汪汪~");
    }
}

  1. 在项目./resources/META-INF/servcie下创建一个文件。文件名为你要动态扩展的接口的全限定名称,我们这里就是Animaal接口的全限定名称,如图所示:

image-20220504201416121

文件里面的内容就是实现类的全限定名称

cuit.epoch.pymjl.spi.impl.Cat
cuit.epoch.pymjl.spi.impl.Dog

  1. 编写测试类

package cuit.epoch.pymjl.spi;

import lombok.extern.slf4j.Slf4j;

import java.util.Iterator;
import java.util.ServiceLoader;

/**
 * @author Pymjl
 * @version 1.0
 * @date 2022/5/2 17:47
 **/
@Slf4j
public class Main {
    public static void main(String[] args) {
        log.info("这是Java原生的SPI机制");
        ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);
        for (Animal animal : serviceLoader) {
            animal.call();
        }

    }
}

  1. 运行测试

image-20220504201720318

通过测试代码可见,我们实现的两个类成功的被加载并运行了相关的方法。但是我们并没有在代码中显示的创建某个实现类,我们可以通过这种插件化的配置文件的形式实例化我们想要的对象。将程序的决定权解耦到配置文件中

但是Java原生的SPI也有其不足

  1. 通过测试代码我们可知,我们并不能直接指定加载某一个我们想要的类,只能通过遍历加载配置文件中所有类来找到我们想要的类,这样效率是很低的,造成资源的浪费
  2. 获取类的方式不够灵活,只能通过Iterator 形式获取,不能根据某个参数来获取对应的实现类。
  3. ServiceLoader线程不安全

针对以上不足,我们可以选择Dubbo的SPI机制,以此来规避不足

Dubbo的SPI

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。

另外,Dubbo是通过键值对的方式进行配置,我们可以直接通过Key获取我们想要加载的实体类。Dubbo默认的配置文件路径是在./resources/META-INF/dubbo

编写测试方法:

package cuit.epoch.pymjl.spi;

import lombok.extern.slf4j.Slf4j;

import java.util.Iterator;
import java.util.ServiceLoader;

/**
 * @author Pymjl
 * @version 1.0
 * @date 2022/5/2 17:47
 **/
@Slf4j
public class Main {
    public static void main(String[] args) {
        log.info("这是Dubbo的SPI机制");
        Animal cat = ExtensionLoader.getExtensionLoader(Animal.class)
                .getExtension("cat");
        Animal dog = ExtensionLoader.getExtensionLoader(Animal.class)
                .getExtension("dog");
        cat.call();
        dog.call();

    }
}

更改配置文件

cat=cuit.epoch.pymjl.spi.impl.Cat
dog=cuit.epoch.pymjl.spi.impl.Dog

运行测试:

image-20220504203927835

通过代码可知,Dubbo的SPI机制可直接通过Key获取我们想要的对象实例,比原生的Java SPI更有优势,除此之外,Dubbo在设计中还用到了大量的全局缓存,提高了我们实例化对象的效率,同时还支持通过注解默认实现,AOP,IOC等功能

下面是仿造Dubbo的源码自定义实现的SPI机制的代码,来自于Guide哥开源的RPC项目。虽然和源码的有一些差别,也没有Dubbo的功能完善,但核心思想还是一样的

仿造源码自定义实现Dubbo SPI机制

自定义注解MySpi

通过上文可知,Dubbo的SPI机制还要根据@spi注解实现,对需要扩展的接口做一个标记,所以我们先自定义一个注解,用来标记我们想要扩展的接口

package cuit.epoch.pymjl.spi;

import java.lang.annotation.*;

/**
 * 标记接口,声明该接口是一个扩展点
 *
 * @author Pymjl
 * @version 1.0
 * @date 2022/5/3 12:05
 **/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MySpi {
     //TODO value可以提供对默认实现的支持,但是这方面的切面并没有写,只是写在这儿表示有这个东西
     String value() default "";
}

Holder对象

顾名思义,Holder就是持有通过ExtensionLoader加载来的实例化对象的对象

package cuit.epoch.pymjl.spi;

/**
 * 持有人
 *
 * @author Pymjl
 * @version 1.0
 * @date 2022/5/3 15:11
 **/
public class Holder<T> {
    private volatile T value;

    public T get() {
        return value;
    }

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

ExtensionLoader

Dubbo SPI机制的核心类,使用它从文件中读取配置,并通过反射实例化对象

package cuit.epoch.pymjl.spi;

import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 模仿Dubbo自定义扩展机制
 *
 * @author Pymjl
 * @version 1.0
 * @date 2022/5/3 15:12
 **/
@Slf4j
public class ExtensionLoader<T> {
    /**
     * SPI配置文件根目录
     */
    private static final String SERVICE_DIRECTORY = "META-INF/diy-rpc/";

    /**
     * 本地缓存,Dubbo会先通过getExtensionLoader方法从缓存中获取一个ExtensionLoader
     * 若缓存未命中,则会生成一个新的实例
     */
    private static final Map<Class<?>, ExtensionLoader<?>> EXTENSION_LOADER_MAP = new ConcurrentHashMap<>();

    /**
     * 目标扩展类的字节码和实例对象
     */
    private static final Map<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();

    /**
     * 需要加载的扩展类类别
     */
    private final Class<?> type;

    /**
     * 本地缓存
     */
    private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();

    /**
     * 扩展类实例对象,key为配置文件中的key,value为实例对象的全限定名称
     */
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

    public ExtensionLoader(Class<?> type) {
        this.type = type;
    }

    /**
     * 得到扩展加载程序
     *
     * @param type 要扩展的接口,必须被MySpi标记
     * @return {@code ExtensionLoader<S>}
     */
    public static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {
        //判断type是否为null
        if (type == null) {
            throw new IllegalArgumentException("Extension type should not be null.");
        }
        //如果不是接口
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type must be an interface.");
        }
        //判断是否被MySpi标记
        if (type.getAnnotation(MySpi.class) == null) {
            throw new IllegalArgumentException("Extension type must be annotated by @MySpi");
        }
        //先从缓存中获取扩展加载器,如果未命中,则创建
        ExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADER_MAP.get(type);
        if (extensionLoader == null) {
            //未命中则创建,并放入缓存
            EXTENSION_LOADER_MAP.putIfAbsent(type, new ExtensionLoader<>(type));
            extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADER_MAP.get(type);
        }
        return extensionLoader;
    }

    /**
     * 得到扩展类对象实例
     *
     * @param name 配置名字
     * @return {@code T}
     */
    public T getExtension(String name) {
        //检查参数
        if (name == null || name.trim().length() == 0) {
            throw new IllegalArgumentException("Extension name should not be null or empty.");
        }
        //先从缓存中获取,如果未命中,新建
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        //如果Holder还未持有目标对象,则为其创建一个单例对象
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

    /**
     * 通过扩展类字节码创建实例对象
     *
     * @param name 名字
     * @return {@code T}
     */
    private T createExtension(String name) {
        //从文件中加载所有类型为 T 的扩展类并按名称获取特定的扩展类
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw new RuntimeException("No such extension of name " + name);
        }
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            try {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
                log.error(e.getMessage());
            }
        }
        return instance;
    }

    /**
     * 获取所有扩展类
     *
     * @return {@code Map<String, Class<?>>}
     */
    private Map<String, Class<?>> getExtensionClasses() {
        //从缓存中获取已经加载的扩展类
        Map<String, Class<?>> classes = cachedClasses.get();
        //双重检查
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = new HashMap<>();
                    //从配置文件中加载所有扩展类
                    loadDirectory(classes);
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

    /**
     * 从配置目录中加载所有扩展类
     *
     * @param extensionsClasses 扩展类的K,V键值对
     */
    private void loadDirectory(Map<String, Class<?>> extensionsClasses) {
        String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();
        try {
            //获取配置文件的资源路径
            Enumeration<URL> urls;
            ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
            urls = classLoader.getResources(fileName);
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    URL resourceUrl = urls.nextElement();
                    loadResource(extensionsClasses, classLoader, resourceUrl);
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * 通过Url加载资源
     *
     * @param extensionClasses 扩展类,key为配置文件中的key,Value为实现类的全限定名称
     * @param classLoader      类加载器
     * @param resourceUrl      资源url
     */
    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), StandardCharsets.UTF_8))) {
            String line;
            //读取文件中的每一行数据
            while ((line = reader.readLine()) != null) {
                //先排除配置文件中的注释
                final int noteIndex = line.indexOf('#');
                //我们应该忽略掉注释后的内容
                if (noteIndex > 0) {
                    line = line.substring(0, noteIndex);
                }
                line = line.trim();
                if (line.length() > 0) {
                    try {
                        final int keyIndex = line.indexOf('=');
                        String key = line.substring(0, keyIndex).trim();
                        String value = line.substring(keyIndex + 1).trim();
                        if (key.length() > 0 && value.length() > 0) {
                            Class<?> clazz = classLoader.loadClass(value);
                            extensionClasses.put(key, clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                        log.error(e.getMessage());
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }

    }
}

  1. 先声明了一些常量,例如配置文件的根目录,本地缓存等

     /**
         * SPI配置文件根目录
         */
        private static final String SERVICE_DIRECTORY = "META-INF/diy-rpc/";
    
        /**
         * 本地缓存,Dubbo会先通过getExtensionLoader方法从缓存中获取一个ExtensionLoader
         * 若缓存未命中,则会生成一个新的实例
         */
        private static final Map<Class<?>, ExtensionLoader<?>> EXTENSION_LOADER_MAP = new ConcurrentHashMap<>();
    
        /**
         * 目标扩展类的字节码和实例对象
         */
        private static final Map<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
    
        /**
         * 需要加载的扩展类类别
         */
        private final Class<?> type;
    
        /**
         * 本地缓存
         */
        private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    
        /**
         * 扩展类实例对象,key为配置文件中的key,value为实例对象的全限定名称
         */
        private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
    
  2. ExtensionLoader 的getExtensionLoader 方法先从缓存中获取一个ExtensionLoader 实例,若缓存未命中,则new一个。

    /**
         * 得到扩展加载程序
         *
         * @param type 要扩展的接口,必须被MySpi标记
         * @return {@code ExtensionLoader<S>}
         */
        public static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {
            //判断type是否为null
            if (type == null) {
                throw new IllegalArgumentException("Extension type should not be null.");
            }
            //如果不是接口
            if (!type.isInterface()) {
                throw new IllegalArgumentException("Extension type must be an interface.");
            }
            //判断是否被MySpi标记
            if (type.getAnnotation(MySpi.class) == null) {
                throw new IllegalArgumentException("Extension type must be annotated by @MySpi");
            }
            //先从缓存中获取扩展加载器,如果未命中,则创建
            ExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADER_MAP.get(type);
            if (extensionLoader == null) {
                //未命中则创建,并放入缓存
                EXTENSION_LOADER_MAP.putIfAbsent(type, new ExtensionLoader<>(type));
                extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADER_MAP.get(type);
            }
            return extensionLoader;
        }
    
  3. 然后再通过ExtensionLoader 的getExtension 方法获取拓展类对象。跟上面的逻辑一样,先从缓存中查找,若缓存未命中则新建一个

   /**
     * 得到扩展类对象实例
     *
     * @param name 配置名字
     * @return {@code T}
     */
    public T getExtension(String name) {
        //检查参数
        if (name == null || name.trim().length() == 0) {
            throw new IllegalArgumentException("Extension name should not be null or empty.");
        }
        //先从缓存中获取,如果未命中,新建
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        //如果Holder还未持有目标对象,则为其创建一个单例对象
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

   /**
     * 通过扩展类字节码创建实例对象
     *
     * @param name 名字
     * @return {@code T}
     */
    private T createExtension(String name) {
        //从文件中加载所有类型为 T 的扩展类并按名称获取特定的扩展类
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw new RuntimeException("No such extension of name " + name);
        }
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            try {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
                log.error(e.getMessage());
            }
        }
        return instance;
    }

    /**
     * 获取所有扩展类
     *
     * @return {@code Map<String, Class<?>>}
     */
    private Map<String, Class<?>> getExtensionClasses() {
        //从缓存中获取已经加载的扩展类
        Map<String, Class<?>> classes = cachedClasses.get();
        //双重检查
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = new HashMap<>();
                    //从配置文件中加载所有扩展类
                    loadDirectory(classes);
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

    /**
     * 从配置文件中加载所有扩展实现类
     *
     * @param extensionsClasses 扩展类的K,V键值对
     */
    private void loadDirectory(Map<String, Class<?>> extensionsClasses) {
        String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();
        try {
            //获取配置文件的资源路径
            Enumeration<URL> urls;
            ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
            urls = classLoader.getResources(fileName);
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    URL resourceUrl = urls.nextElement();
                    loadResource(extensionsClasses, classLoader, resourceUrl);
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * 通过Url加载资源
     *
     * @param extensionClasses 扩展类,key为配置文件中的key,Value为实现类的全限定名称
     * @param classLoader      类加载器
     * @param resourceUrl      资源url
     */
    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), StandardCharsets.UTF_8))) {
            String line;
            //读取文件中的每一行数据
            while ((line = reader.readLine()) != null) {
                //先排除配置文件中的注释
                final int noteIndex = line.indexOf('#');
                //我们应该忽略掉注释后的内容
                if (noteIndex > 0) {
                    line = line.substring(0, noteIndex);
                }
                line = line.trim();
                if (line.length() > 0) {
                    try {
                        final int keyIndex = line.indexOf('=');
                        String key = line.substring(0, keyIndex).trim();
                        String value = line.substring(keyIndex + 1).trim();
                        if (key.length() > 0 && value.length() > 0) {
                            Class<?> clazz = classLoader.loadClass(value);
                            extensionClasses.put(key, clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                        log.error(e.getMessage());
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }


---------------------
作者:Pymj
来源:CSDN
原文:https://blog.csdn.net/apple_52109766/article/details/124577928
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

最后

以上就是明亮冰棍为你收集整理的Dubbo中的SPI机制Dubbo中的SPI机制概述Java原生的SPIDubbo的SPI仿造源码自定义实现Dubbo SPI机制的全部内容,希望文章能够帮你解决Dubbo中的SPI机制Dubbo中的SPI机制概述Java原生的SPIDubbo的SPI仿造源码自定义实现Dubbo SPI机制所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(50)

评论列表共有 0 条评论

立即
投稿
返回
顶部