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);
}
// 单例模式创建对象,双检测锁。没有只是使用ConcurrentHashMap
Object 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,扩展类加载器,然后设置文件的URl
ClassLoader 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. 第二次检查:防止其他线程已创建holder
holder = 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. 第二次检查:防止其他线程已创建holder
holder = 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. 第二次检查:防止其他线程已创建holder
holder = 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机制了
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 {
@Test
public void testExtention() {
LoadBalance loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension("loadBalanceNew");
}
}