概述
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 {
@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("狗叫:汪汪汪~");
}
}
- 在项目
./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是否为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());
}
}
}
-
先声明了一些常量,例如配置文件的根目录,本地缓存等
/** * 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是否为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; }
-
然后再通过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机制所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复