Dubbo中的SPI机制
概述
Service Provider Interface
即 SPI,是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件。可以让不同的厂商针对统一接口编写不同的实现
SPI实际上是“接口+策略模式+配置文件”实现的动态加载机制。在系统设计中,模块之间通常基于接口编程,不直接显示指定实现类。一旦代码里指定了实现类,就无法在不修改代码的情况下替换为另一种实现。为了达到动态可插拔的效果,java提供了SPI以实现服务发现。
SPI机制的应用场景有很多,我们比较常用的就是JDBC,Dubbo等
在谈Dubbo的SPI机制前我们需要先了解一下Java原生的SPI机制
Java原生的SPI
- 首先我,我们先创建一个接口,接口中定义一个方法
package cuit.epoch.pymjl.spi;/*** @author Pymjl* @version 1.0* @date 2022/5/2 17:43**/
public interface Animal {/*** 动物叫*/void call();
}
- 然后我们分别写两个不同的实现类,实现这个接口
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 {@Overridepublic 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 {@Overridepublic void call() {System.out.println("狗叫:汪汪汪~");}
}
- 在项目
./resources/META-INF/servcie
下创建一个文件。文件名为你要动态扩展的接口的全限定名称,我们这里就是Animaal
接口的全限定名称,如图所示:
文件里面的内容就是实现类的全限定名称
cuit.epoch.pymjl.spi.impl.Cat
cuit.epoch.pymjl.spi.impl.Dog
- 编写测试类
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();}}
}
- 运行测试
通过测试代码可见,我们实现的两个类成功的被加载并运行了相关的方法。但是我们并没有在代码中显示的创建某个实现类,我们可以通过这种插件化的配置文件的形式实例化我们想要的对象。将程序的决定权解耦到配置文件中
但是Java原生的SPI也有其不足:
- 通过测试代码我们可知,我们并不能直接指定加载某一个我们想要的类,只能通过遍历加载配置文件中所有类来找到我们想要的类,这样效率是很低的,造成资源的浪费
- 获取类的方式不够灵活,只能通过Iterator 形式获取,不能根据某个参数来获取对应的实现类。
- 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
运行测试:
通过代码可知,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是否为nullif (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());}}
}
-
先声明了一些常量,例如配置文件的根目录,本地缓存等
/*** 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<>();
-
ExtensionLoader 的getExtensionLoader 方法先从缓存中获取一个ExtensionLoader 实例,若缓存未命中,则new一个。
/*** 得到扩展加载程序** @param type 要扩展的接口,必须被MySpi标记* @return {@code ExtensionLoader<S>}*/public static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {//判断type是否为nullif (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;}
-
然后再通过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博客文章一键转载插件