


























SPI, Service Provider Interface.
There are three essential components of a service provider framework:
a service interface, which providers implement;
a provider registration API, which the system uses to register implementations, giving clients access to them;
and a service access API, which clients use to obtain an instance of the service.
service provider framework 有三个重要的组件,
service interface, 提供实现
供应者的注册接口,可以用来注册接口实现,这样就可以访问到实现类。
获取 service 的api,可以用来获取 service 的实例。
SPI 主要是被框架的开发人员使用,比如 java.sql.Driver 接口,其他不同厂商可以针对同一接口做出不同的实现,mysql 和 postgresql 都有不同的实现提供给用户。Java 的 SPI 机制可以为某个接口注册服务实现。
Java 的 SPI 实现是由 java.util.ServiceLoader 类实现。当服务的提供者提供了一种接口的实现之后,需要在 classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的 META-INF/services/ 中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。
这是一个 service provider framework 大致结构
// Service provider framework sketch
// Service 接口,我们要使用的功能在这里
// Service interface
public interface Service {
... // Service-specific methods go here
}
// Provider 用来获取 Service 实例
// Service provider interface
public interface Provider {
Service newService();
}
// Noninstantiable class for service registration and access
public class Services {
private Services() { } // Prevents instantiation (Item 4)
// 用来维持 Provider 实例的 Map
// Maps service names to services
private static final Map<String, Provider> providers =
new ConcurrentHashMap<String, Provider>();
public static final String DEFAULT_PROVIDER_NAME = "<def>";
// 注册 Provider 到 Map
// Provider registration API
public static void registerDefaultProvider(Provider p) {
registerProvider(DEFAULT_PROVIDER_NAME, p);
}
public static void registerProvider(String name, Provider p){
providers.put(name, p);
}
// Service 获取接口
// Service access API
public static Service newInstance() {
return newInstance(DEFAULT_PROVIDER_NAME);
}
// 生成 Service 实例
public static Service newInstance(String name) {
Provider p = providers.get(name);
if (p == null)
throw new IllegalArgumentException(
"No provider registered with name: " + name);
return p.newService();
}
}
上面的一段代码复现了上一节中提到的三个重要的组件。理解这段代码,对 SPI 机制原理的理解非常重要。
至于为什么要基于 Interface 去做 SPI,那是因为读入的类你是不知道他的具体类的,也并不知道他有哪些方法,因为这是运行时加载的,编译期都没法获取。具体见下面伪代码:
// 如果不使用 interface
// 比如现在要导入一个 Student 类
class Student {
public void say () { println("hello, i'm a student"); }
}
// 平常使用
main () {
// 如果是 基于 SPI 运行时注册的类,你是不知道他的类名的,就像你并不知道他叫 Student
// 即使你知道他类名叫 Student,也没有用,因为这里的 Student 类需要 import 进来,而编译时并没有这个类,就没办法 import
Student stu = new Student();
stu.say();
}
// 基于 SPI
interface Person {
public void say();
}
class Student implements Person {
public void say () { println("hello, i'm a student"); }
}
main () {
// 动态加载类,使用 cast 就可以得到一个 Person接口的实例,就可以调用 say() 方法了
// 这里是直接获取 Service,可以看到能得到的 Service 是受限的,所以可以使用一个 Provider 并提供一个 API 来获取 Service,以期更完美的实现。(参看上面的三个重要组件。)
Person stu = (Person) Class.forName("com.xx.Student").newInstance();
stu.say();
}
JDK 的 SPI 实现是由 java.util.ServiceLoader 类实现。
以下是 ServiceLoader 类的成员变量:
public final class ServiceLoader<S>
implements Iterable<S>
{ // 可以看到默认的寻找配置的地址是 META-INF/services/
private static final String PREFIX = "META-INF/services/";
// 使用 Class.forName 加载到配置文件中的对象,使用 service.cast(newInstance) 强转类型
// The class or interface representing the service being loaded
private Class<S> service;
// 类加载器,如果为null,默认使用 systemClassLoader
// The class loader used to locate, load, and instantiate providers
private ClassLoader loader;
// 存储 provider 的集合,存的是 service 的实例[service.cast(newInstance)]
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 迭代器,迭代过程中实例化 service并存到 map中
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
...
}
其实原理也很简单,用目标对象 Interface 作为泛型,这样就能利用 Interface 的全限定名查找 META-INF/services/ 下的文件,然后一行一行读取文件,加载 providers 到 map 中。所以 ServiceLoader 类是加载配置文件中全部的类的实例的,而且是一次性加载完成。
由上可知,整个类的其他部分就是在实现查找文件 => 获取类加载器 => 加载 class 对象 => cast 到指定 Interface 并存入 map,而整个过程都是在迭代器中完成的(iterator.next() 方法)。
哦,对了,重新加载直接调用 reload() 方法就好了,方法实现就是新建一个 LazyIterator,然后重复上面的动作。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 nickchenyx@gmail.com
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。