SPI机制简介
SPI(Service Provider Interface)是一种服务发现机制,允许框架在运行时动态加载和使用实现。它将服务接口与具体实现解耦,使框架可以灵活扩展功能
Dubbo 的 SPI机制是其核心特性之一,用于实现框架的可扩展设计。它借鉴了 Java 标准 SPI,但功能更强大,支持 自动注入依赖、自适应扩展、URL 参数动态选择 等高级特性。其中
● 自动注入依赖,是使用了反射,然后通过Setter方法,进行依赖注入
● 自适应扩展和URL参数动态选择,是基于@Adaptive注解,可以在运行时候,通过URL,对需要注入类进行选择。
本文,只是实现了Dubbo的SPI基本类加载、对象缓存的功能,并没有实现自动依赖注入,自适应扩展,URL,后续可以参照Dubbo源码进行改进。
1. 类加载
可以采用Dubbo源码的SPI机制,去掉Adaptive,直接加载全部的自类的实现。
ExtentionClassLoader(Class<?>type)
:SPI机制的核心就是扩展类加载器,核心方法就是getExtension(String name);
也就是说,创建type
接口的,名称为name
的一个实例。
package github.javaguide.extension;import github.javaguide.factory.SingletonFactory;
import github.javaguide.utils.StringUtil;
import lombok.extern.slf4j.Slf4j;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;import static java.nio.charset.StandardCharsets.UTF_8;/*** refer to dubbo spi: https://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html*/
@Slf4j
public final class ExtensionLoader<T> {private static final String SERVICE_DIRECTORY = "META-INF/extensions/";/*** 扩展类加载器的缓存,每一个类都有一个扩展类加载器。* 需要考虑多线程的问题* */private static final Map<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();/*** 扩展类实力的缓存,根据全类名进行缓存* */
// private static final Map<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();private final Class<?> type;/*** 实力缓存,根据名字进行缓存* 保证可见性的 Holder。* */private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();/*** 类缓存,根据名称进行缓存,从文件中进行读取的key,value* */private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();private ExtensionLoader(Class<?> type) {this.type = type;}/*** 获取扩展类加载器* */public static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {if (type == null) {throw new IllegalArgumentException("Extension type should not be null.");}if (!type.isInterface()) {// 需要是接口throw new IllegalArgumentException("Extension type must be an interface.");}if (type.getAnnotation(SPI.class) == null) {// 类上需要包含SPI注解throw new IllegalArgumentException("Extension type must be annotated by @SPI");}// 创建类加载器,直接就是使用ConcurrentHashMap进行创建的,每一个类有一个自己的类加载器ExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);if (extensionLoader == null) {EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<S>(type));extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);}return extensionLoader;}public T getExtension(String name) {if (StringUtil.isBlank(name)) {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);}// 单例模式创建对象,双检测锁。没有只是使用ConcurrentHashMapObject instance = holder.get();if (instance == null) {synchronized (holder) {instance = holder.get();if (instance == null) {instance = createExtensionNew(name);holder.set(instance);}}}return (T) instance;}/*** 使用SigletonFactory创建单例bean* */private T createExtensionNew(String name) {// 1. 首先获取扩展类加载器Class<?> clazz = getExtensionClasses().get(name);if (clazz == null) {throw new RuntimeException("扩展类不存在: " + name);}// 2. 获取实例return (T) SingletonFactory.getInstance(clazz);}private Map<String, Class<?>> getExtensionClasses() {// 1. 从缓存中获取所有的类Map<String, Class<?>> classes = cachedClasses.get();// 2. 缓存中没有,进行双检测锁if (classes == null) {synchronized (cachedClasses) {classes = cachedClasses.get();if (classes == null) {classes = new HashMap<>();// 3. 从文件夹中加载所有的扩展类loadDirectory(classes);cachedClasses.set(classes);}}}return classes;}/*** java的SPI机制* */private void loadDirectory(Map<String, Class<?>> extensionClasses) {// 1. 构建配置文件的路径String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();try {Enumeration<URL> urls;// 2. Java的SPI,扩展类加载器,然后设置文件的URlClassLoader classLoader = ExtensionLoader.class.getClassLoader();urls = classLoader.getResources(fileName);if (urls != null) {while (urls.hasMoreElements()) {URL resourceUrl = urls.nextElement();// 3. 加载并解析loadResource(extensionClasses, classLoader, resourceUrl);}}} catch (IOException e) {log.error(e.getMessage());}}private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) {try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {String line;// 读取配置文件的没一行while ((line = reader.readLine()) != null) {// 1. 过滤掉注释final int ci = line.indexOf('#');if (ci >= 0) {line = line.substring(0, ci);}// 2. 去掉空格line = line.trim();if (line.length() > 0) {try {// 3. = 实现的key value对解析,存入到map中final int ei = line.indexOf('=');String name = line.substring(0, ei).trim();String clazzName = line.substring(ei + 1).trim();if (name.length() > 0 && clazzName.length() > 0) {// 4. Java的SPI的具体实现Class<?> clazz = classLoader.loadClass(clazzName);extensionClasses.put(name, clazz);}} catch (ClassNotFoundException e) {log.error(e.getMessage());}}}} catch (IOException e) {log.error(e.getMessage());}}
}
上述的流程:
- 首先,解析自定义的类文件,得到字类的名字(key),和全类名(value)
- 其次,通过Java的应用类加载器,加载字类
- 最后,将子类放入缓存中
注意:上述代码虽然可以创建一个实例,但是没有进行依赖注入,也就是说,对于有参类,没有只能进行实例化,没有办法进行初始化,如果SPI的子类有成员变量,访问成员变量的时候,就会报空指针异常。
2. 单例工厂
上面代码的代码中,有个自定义的单例工厂,类实现如下:
核心的逻辑,就是双检测锁实现的线程安全的单例模式。
package github.javaguide.factory;import github.javaguide.extension.Holder;
import lombok.extern.slf4j.Slf4j;import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;/*** 获取单例对象的工厂类** @author shuang.kou* @createTime 2020年06月03日 15:04:00*/
@Slf4j
public final class SingletonFactory {private static final Map<String, Object> OBJECT_MAP = new ConcurrentHashMap<>();private static final Object lock = new Object();private static final Map<String, Holder<Object>> OBJECT_MAP_NEW = new HashMap<>();private SingletonFactory() {}public static <T> T getInstance(Supplier<T> constructor, Class<T> c) {if (c == null) {throw new IllegalArgumentException("Class cannot be null");}String key = c.getName();// 1. 第一次检查:快速读取缓存(无锁)Holder<Object> holder = OBJECT_MAP_NEW.get(key);if (holder != null && holder.get() != null) {// 1.1 holder保证了可见性,从而不会使用没有初始化的对象return c.cast(holder.get());}// 2. 同步块:确保只有一个线程创建实例synchronized (lock) {// 3. 第二次检查:防止其他线程已创建holderholder = OBJECT_MAP_NEW.computeIfAbsent(key, k -> new Holder<>());// 4. 创建实例(此处不需要再次检查holder.get(),因为锁保证了互斥性)if (holder.get() == null) {try {// 4.1 创建对象T instance = constructor.get();// 4.2 放入到map里面holder.set(instance);} catch (Exception e) {throw new RuntimeException("创建示例失败", e);}}}return c.cast(holder.get());}public static <T> T getInstance(Consumer<T> initConsumer, Class<T> c) {if (c == null) {throw new IllegalArgumentException("Class cannot be null");}String key = c.getName();// 1. 第一次检查:快速读取缓存(无锁)Holder<Object> holder = OBJECT_MAP_NEW.get(key);if (holder != null && holder.get() != null) {// 1.1 holder保证了可见性,从而不会使用没有初始化的对象return c.cast(holder.get());}// 2. 同步块:确保只有一个线程创建实例synchronized (lock) {// 3. 第二次检查:防止其他线程已创建holderholder = OBJECT_MAP_NEW.computeIfAbsent(key, k -> new Holder<>());// 4. 创建实例(此处不需要再次检查holder.get(),因为锁保证了互斥性)if (holder.get() == null) {try {// 4.1 创建对象T instance = c.getDeclaredConstructor().newInstance();// 4.2 初始化对象initConsumer.accept(instance);// 4.2 放入到map里面holder.set(instance);} catch (Exception e) {throw new RuntimeException("创建示例失败", e);}}}return c.cast(holder.get());}public static <T> T getInstance(Class<T> c) {if (c == null) {throw new IllegalArgumentException("Class cannot be null");}String key = c.getName();// 1. 第一次检查:快速读取缓存(无锁)Holder<Object> holder = OBJECT_MAP_NEW.get(key);if (holder != null && holder.get() != null) {// 1.1 holder保证了可见性,从而不会使用没有初始化的对象return c.cast(holder.get());}// 2. 同步块:确保只有一个线程创建实例synchronized (lock) {// 3. 第二次检查:防止其他线程已创建holderholder = OBJECT_MAP_NEW.computeIfAbsent(key, k -> new Holder<>());// 4. 创建实例(此处不需要再次检查holder.get(),因为锁保证了互斥性)if (holder.get() == null) {try {Constructor<T> constructor = c.getDeclaredConstructor();constructor.setAccessible(true);T instance = constructor.newInstance();// 4.2 放入到map里面holder.set(instance);} catch (Exception e) {throw new RuntimeException("创建示例失败", e);}}}return c.cast(holder.get());}public static <T> T getInstanceOld(Class<T> c) {if (c == null) {throw new IllegalArgumentException();}String key = c.toString();if (OBJECT_MAP.containsKey(key)) {return c.cast(OBJECT_MAP.get(key));} else {synchronized (lock) {if (!OBJECT_MAP.containsKey(key)) {try {T instance = c.getDeclaredConstructor().newInstance();OBJECT_MAP.put(key, instance);return instance;} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {throw new RuntimeException(e.getMessage(), e);}} else {return c.cast(OBJECT_MAP.get(key));}}}}
}
上面的类中,getInstance(Consumer<T> initConsumer, Class<T> c)
这个方法,提供了带有初始化方法实例获取。可以解决实例化后,无法初始化的问题。
3. SPI的测试
使用maven工程,引入unit4的测试模块,就可以用下面的方法,测试SPI机制了
- 首先在resources文件夹下,创建/META-INF/extention文件夹
- 其次创建文件,文件名称为接口的全类名
- 然后,文件中的内容,类似
loadBalanceNew=github.javaguide.loadbalance.loadbalancer.ConsistentHashLoadBalance
- 最后,就可以通过下面的代码实现服务发现了
package github.javaguide;import github.javaguide.extension.ExtensionLoader;
import github.javaguide.loadbalance.LoadBalance;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;/*** @author: Zekun Fu* @date: 2025/6/8 19:58* @Description:*/
@Slf4j
public class ExtentionTest {@Testpublic void testExtention() {LoadBalance loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension("loadBalanceNew");}
}