概述
SPI,Service Provider Interface,一种服务发现机制,指一些提供给你继承、扩展,完成自定义功能的类、接口或方法。
在SPI机制中,服务提供者为某个接口实现具体的类,而在运行时通过SPI机制,查找到对应的实现类,并将其加载进行使用。
JDK 6(参考java.util.ServiceLoader
类的注释Since字段)引进的一个特性,可实现动态加载具体实现类的机制,通过它可以具体实现代码的解耦,也可实现类似于IoC的效果。
SPI的实现方式:提供实现的实现类打包成jar文件,jar文件里面必须有个META-INF/services
目录,其下有一个文本文件,文件名即为SPI接口的全限定名(完整路径名),文件的内容是该jar包中提供的SPI接口的实现类名。文件编码是UTF-8。
作用有两个:
- 把标准定义和接口实现分离,在模块化开发中很好地实现解耦
- 实现功能的扩展,更好地满足定制化的需求
SPI的不足之处:不能根据需求去加载扩展实现,每次都会加载扩展接口的所有实现类并进行实例化,实例化会造成性能开销,并且加载一些不需要用到的实现类,会导致内存资源的浪费。
API和SPI
API,提供给他人使用的具备某项功能的类、接口或方法。
SPI用于一些通用的标准中,为标准的实现产商提供扩展点。标准在上层提供API,API内部使用SPI,当API被客户使用时,会动态得从当前运行的classpath中寻找该SPI的实现,然后使用该SPI的实现来完成API的功能。
实例
JDBC
java.sql.Driver
数据库驱动接口,JDK中只提供接口的定义,具体的实现类由各个数据库厂商提供的驱动包来完成,程序在运行的时候会根据当前导入的驱动包来完成对应数据库的连接。
如上图,java.sql.Driver
文件内容:com.mysql.cj.jdbc.Driver
。
同理也能在postgresql-42.7.2.jar
下找到java.sql.Driver
文件,内容为org.postgresql.Driver
,截图省略。
com.mysql.cj.jdbc.Driver
源码很简单:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
// Register ourselves with the DriverManager.
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
}
}
核心方法就一行:DriverManager.registerDriver(new Driver());
,在PG源码org.postgresql.Driver
里也能找到DriverManager.registerDriver()
。
基于JDK 22源码,在DriverManager
类里搜索文件名java.sql.Driver
,找到方法ensureDriversInitialized:
/*
* Load the initial JDBC drivers by checking the System property jdbc.drivers and then use the ServiceLoader mechanism
*/
private static void ensureDriversInitialized() {
// 检查驱动是否已经初始化成功
if (driversInitialized) {
return;
}
synchronized (lockForInitDrivers) {
// 优先从环境变量jdbc.drivers获取驱动
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// ServiceLoader
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* 加载驱动然后初始化,存在一种情况,有java.sql.Driver文件,但是文件内容为空,
* 或找不到文件内容指向的Driver类,用Throwable来catch java.util.ServiceConfigurationError
*/
try {
// TODO: loadedDrivers并没有赋值到drivers?
while (driversIterator.hasNext()) {
driversIterator.next();
}
} catch (Throwable t) {
// Do nothing
}
return null;
}
});
if (drivers != null && !drivers.isEmpty()) {
String[] driversList = drivers.split(":");
for (String aDriver : driversList) {
try {
Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
// just logging
}
}
}
// 设置驱动初始化成功标志
driversInitialized = true;
}
}
ensureDriversInitialized()
方法的调用方有getDriver()
和getConnection()
两个,即获取驱动和获取连接。
Spring
在Spring源码里也有SPI的影子,如spring.factories
文件和SpringFactoriesLoader,两者一起构成Spring Boot Starter实现的基础。
以3.2.4版本来讲解,位于spring-boot-actuator-autoconfigure-3.2.4.jar
下的spring.factories
文件片段:
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.actuate.autoconfigure.metrics.ValidationFailureAnalyzer,\
org.springframework.boot.actuate.autoconfigure.health.NoSuchHealthContributorFailureAnalyzer
可见,此文件实际上是.properties
文件格式,不同类型的配置项用空行隔开,方便阅读,支持#
作为注释字符。配置是键值对形式,Key和Value都是代表类的完整包名,Value可以有多个,用,
分隔,多行文本以\
分隔;并且Key是接口、注解、或抽象类,Value是Key的实现类。
而SpringFactoriesLoader自spring 3.2版本开始就已经存在,其属性如下:
// spring.factories文件默认检索位置
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// 默认抛出异常
private static final FailureHandler THROWING_FAILURE_HANDLER = FailureHandler.throwing();
// ClassLoader级别的缓存,Value也是Map,K为资源目录,V为全路径类
static final Map<ClassLoader, Map<String, SpringFactoriesLoader>> cache = new ConcurrentReferenceHashMap<>();
// 用于加载类
@Nullable
private final ClassLoader classLoader;
// 用于保存spring.factories文件里的键值对
private final Map<String, List<String>> factories;
核心方法有两个,forResourceLocation是一个静态方法,用于获取实例:
public static SpringFactoriesLoader forResourceLocation(String resourceLocation, @Nullable ClassLoader classLoader) {
Assert.hasText(resourceLocation, "'resourceLocation' must not be empty");
ClassLoader resourceClassLoader = (classLoader != null ? classLoader : SpringFactoriesLoader.class.getClassLoader());
Map<String, SpringFactoriesLoader> loaders = cache.computeIfAbsent(resourceClassLoader, key -> new ConcurrentReferenceHashMap<>());
return loaders.computeIfAbsent(resourceLocation, key -> new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation)));
}
参考resourceLocation
可以传入指定的路径,如果为空,则从META-INF/spring.factories
查找。
load方法则用于获取指定类型的实现类集合:
public <T> List<T> load(Class<T> factoryType, @Nullable ArgumentResolver argumentResolver,
@Nullable FailureHandler failureHandler) {
Assert.notNull(factoryType, "'factoryType' must not be null");
List<String> implementationNames = loadFactoryNames(factoryType);
List<T> result = new ArrayList<>(implementationNames.size());
FailureHandler failureHandlerToUse = (failureHandler != null) ? failureHandler : THROWING_FAILURE_HANDLER;
for (String implementationName : implementationNames) {
T factory = instantiateFactory(implementationName, factoryType, argumentResolver, failureHandlerToUse);
if (factory != null) {
result.add(factory);
}
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
Dubbo
打开dubbo-3.2.14
源码,发现META-INF/services
目录下有个org.apache.dubbo.common.extension.LoadingStrategy
文本文件,内容如下:
org.apache.dubbo.common.extension.DubboInternalLoadingStrategy
org.apache.dubbo.common.extension.DubboLoadingStrategy
org.apache.dubbo.common.extension.ServicesLoadingStrategy
DubboInternalLoadingStrategy用于查找META-INF/dubbo/internal/
目录下的文件,DubboLoadingStrategy用于查找META-INF/dubbo/
目录下的文件,ServicesLoadingStrategy用于查找META-INF/services/
目录下的文件。
META-INF/dubbo/internal/
目录下有91个文件,所有文件名都是Dubbo下的全路径名指向的接口类,这些接口都添加有注解@SPI(value = "", scope = FRAMEWORK)
,文件内容则是多行键值对,采用Key=Value形式,Key是一个简称,Value则是Dubbo下接口(文件名对应的)的实现类。
SPI注解源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
String value() default "";
ExtensionScope scope() default ExtensionScope.APPLICATION;
}
ExtensionScope是一个枚举类:
- FRAMEWORK:
- APPLICATION:默认值
- MODULE:
- SELF:
ExtensionLoader是个泛型类,入口方法是getExtension:
public T getExtension(String name, boolean wrap) {
checkDestroyed();
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
// 获取默认拓展实现类
return getDefaultExtension();
}
String cacheKey = name;
if (!wrap) {
cacheKey += "_origin";
}
// Holder机制包装实现类+缓存
final Holder<Object> holder = getOrCreateHolder(cacheKey);
Object instance = holder.get();
// DCL
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 核心方法:创建实现类
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
return (T) instance;
}
接下来看看createExtension方法:
private T createExtension(String name, boolean wrap) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name)) {
throw findException(name);
}
try {
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
instance = (T) extensionInstances.get(clazz);
// 实例化前处理
instance = postProcessBeforeInitialization(instance, name);
// 向实例中注入依赖
injectExtension(instance);
// 实例化后处理
instance = postProcessAfterInitialization(instance, name);
}
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
boolean match = (wrapper == null) || ((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) && !ArrayUtils.contains(wrapper.mismatches(), name));
if (match) {
// 将当前instance作为参数传给Wrapper的构造方法,并通过反射创建Wrapper实例,向Wrapper实例中注入依赖,最后将Wrapper实例再次赋值给instance变量
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
instance = postProcessAfterInitialization(instance, name);
}
}
}
}
// Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
initExtension(instance);
return instance;
} catch (Throwable t) {
// 创建实现类失败
throw new IllegalStateException();
}
}
核心方法是getExtensionClasses:
private Map<String, Class<?>> getExtensionClasses() {
// 检查缓存
Map<String, Class<?>> classes = cachedClasses.get();
// DCL
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
try {
classes = loadExtensionClasses();
} catch (InterruptedException e) {
logger.error();
throw new IllegalStateException();
}
cachedClasses.set(classes);
}
}
}
return classes;
}
继续看loadExtensionClasses:
private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {
checkDestroyed();
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy, type.getName());
// compatible with old ExtensionFactory
if (this.type == ExtensionInjector.class) {
loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
}
}
return extensionClasses;
}
继续看loadDirectory,两个逻辑,一是调用方法loadDirectoryInternal,二是判断Dubbo 2的向下兼容性,核心方法是loadDirectoryInternal,继续调用方法loadResource,用于读取和解析配置文件,并通过反射加载类,这里面可看到对前面提到的文本文件进行逐行解析,判断=
分隔符的逻辑,最后调用 loadClass方法用于操作缓存。loadClass方法调用缓存相关方法,如cachedAdaptiveClass、cachedWrapperClasses和cachedNames等。