A service is a well-known set of interfaces and (usually abstract) classes. A service provider(SPI) is a specific implementation of a service. The classes in a provider typically implement the interfaces and subclass the classes defined in the service itself. Service providers can be installed in an implementation of the Java platform in the form of extensions, that is, jar files placed into any of the usual extension directories. Providers can also be made available by adding them to the application's class path or by some other platform-specific means.
SPI 全称 Service Provider Interface,是 Java 提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI 的作用就是为这些被扩展的 API 寻找服务实现。这一机制为很多框架扩展提供了可能,比如在 Dubbo、JDBC 中都使用到了 SPI 机制。本文介绍了 Java SPI 机制以及在模块化
和非模块化
项目中的实现方式(此处的模块化指 Java9 引入的模块化)。
服务提供方需要通过一些约定告诉系统自己所提供的服务的位置,Java9 之后一共有两种
约定方式:
META-INF/services/
目录下配置相关文件实现;module-info.java
中添加导出语句指定服务位置实现;package java.util;
public final class ServiceLoader<S> implements Iterable<S>{
private final class LazyClassPathLookupIterator<T> implements Iterator<Provider<T>>{
static final String PREFIX = "META-INF/services/";
Set<String> providerNames = new HashSet<>(); // to avoid duplicates
Enumeration<URL> configs;
Iterator<String> pending;
//.........
}
}
package java.lang;
public final class Module implements AnnotatedElement {
private Class<?> loadModuleInfoClass() {
Class<?> clazz = null;
try (InputStream in = getResourceAsStream("module-info.class")) {
if (in != null)
clazz = loadModuleInfoClass(in);
} catch (Exception ignore) { }
return clazz;
}
/**
* Loads module-info.class as a package-private interface in a class loader
* that is a child of this module's class loader.
*/
private Class<?> loadModuleInfoClass(InputStream in) throws IOException {
final String MODULE_INFO = "module-info";
// . . .
}
}
获取到 SPI 服务实现类的文件之后,就可以使用类加载器将对应的类加载到内存中, 问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的双亲委派模型无法解决这个问题。所以 java 采用了线程上下文类加载器。破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器,从而实现 SPI 服务的加载。线程上下文类加载器的实现如下:
setContextClassLoader(ClassLoader cl)
存储当前线程中的类加载器,默认为 AppClassLoader。getContextClassLoader(ClassLoader cl)
方法获取上下文类加载器,然后通过该类加载器加载 SPI 的实现类。// ServiceLoader 是JDK用于加载 SPI 服务实现类的工具,可以处理 0 个、1 个或者多个服务提供商的情况。
package java.util;
public final class ServiceLoader<S> implements Iterable<S>{
@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}
}
Spring中使用的类是SpringFactoriesLoader,文件路径不同 spring配置放在 META-INF/spring.factories
中:
package org.springframework.core.io.support;
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
}
Dubbo 中的扩展能力是从 JDK 标准的 SPI 扩展点发现机制加强而来,它改进了 JDK 标准的 SPI 以下问题:
用户能够基于 Dubbo 提供的扩展能力,很方便基于自身需求扩展其他协议、过滤器、路由等。下面介绍下 Dubbo 扩展能力的特性。