Service Provider Interface 介绍

用户头像
Skysper
关注
发布于: 2020 年 05 月 24 日

在java6中引入了服务发现和加载的接口:SPI

SPI的使用

创建相关类

package com.example.demo;
//相关类
public interface MyService {
void say();
}

public class HelloWorldService implements MyService {
@Override
public void say() {
System.out.println("Hello world!");
}
}

public class WelcomeService implements MyService {
@Override
public void say() {
System.out.println("Welcome!");
}
}
//其他更多的服务类...

增加文件配置

在Resources目录下create建META-INF/services目录,在目录中创建文件,文件名称为:com.example.demo.MyService

文件中配置我们需要提供服务的类的全路径名称(包名+类名)

com.example.demo.HelloWorldService
com.example.demo.WelcomeService



加载服务

ServiceLoader<MyService> serviceLoader = ServiceLoader.load(MyService.class);
Iterator<MyService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
iterator.next().say();
}

输出结果:

Hello world!

Welcome!



SPI实现原理

ServiceLoader类介绍

从示例中,我们可以看到,SPI的实现主要在ServiceLoader类中。

ServiceLoader实现Iterable接口,所以我们可以直接使用foreach的方式循环获取加载的实现类:

for (MyService myService : serviceLoader) {
myService.say();
}



ServiceLoader有三种加载方法可供使用

//自定义类加载器
load(Class<S>, ClassLoader)
//使用当前线程的Context Class Loader
load(Class<S>)
//使用扩展类加载器加载
loadInstalled(Class<S>)

在load静态方法中,实现最终的ServiceLoader类创建,指定我们的Service和所需要使用的类加载器。

在ServiceLoader类的实例化构造函数执行如下:

private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}

最终在reload方法实现缓存清理和Iterator查找类:

public void reload() {
//清空缓存
//providers类型为LinkedHashMap<String,S>,键为我们的类全名,值为对应
providers.clear();
//实例化赋值LazyIterator
lookupIterator = new LazyIterator(service, loader);
}

其中,LazyIterator是SPI实现的核心

LazyIterator

Lazyiterator用于锁定资源文件,并负责加载文件资源、按行读取解析、实例化对应对象,并且LazyIterator提供了延迟加载,在ServiceLoader创建完毕后,并不会发生资源文件的读取和使用,仅在查找和使用时,才发生资源的初始读取。

//configs为资源集合
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}

加载资源文件

在这里,也定义了我们在使用ServiceLoader的配置规则:

private static final String PREFIX = "META-INF/services/";
//所代表的需要加载的服务类或者接口类
// The class or interface representing the service being loaded
private final Class<S> service;

加载时根据需要加载的类名称和PREFIX目录前缀确定文件并加载:

//全类名
String fullName = PREFIX + service.getName();
//获取资源
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);



读取和验证

private boolean hasNextService() {
//省略其他代码....
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
//读取每个资源
//每个成功资源完全枚举结束后,再加载额外的其他资源
//摊薄io时间复杂度
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}

读取时,按行进行解析,需要确保每行字符内容是符合java类标准的定义形式:

//
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
List<String> names)
throws IOException, ServiceConfigurationError
{
String ln = r.readLine();
if (ln == null) {
return -1;
}
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
int cp = ln.codePointAt(0);
if (!Character.isJavaIdentifierStart(cp))
fail(service, u, lc, "Illegal provider-class name: " + ln);
for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
cp = ln.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
fail(service, u, lc, "Illegal provider-class name: " + ln);
}
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
return lc + 1;
}

规则:

1、每行的类名称中不能包含空字符和水平制表符\t

2、首字符符合java标识符定义

3、对剩余的字符判定是否符合java标识符(首字符以外的规范)

4、同一命名空间下的类仅创建一个,不过对于 !names.contains(ln)这里的判定,如果类实现过多,contains查询会存在性能问题(contains方法时间复杂度O(n),极端情况下值得优化)



实例化对象

对象在真正使用的时候,才进行创建

while(iterator.hasNext()) {
//do nothing.
}

如果仅做next查询,是不会进行类实例化的。

类实例化,需要调用iterator.next()真正获取对象时发生



Class<?> c = null;
//查找加载类
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
//判定类或接口的继承性
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
//创建类
//类型转换
//并将结果类放入providers缓存
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}

以上最终实现服务类的实现



在ServiceLoader的实现中,由于存在providers存在,因此当providers中存在数据时,在使用ServiceLoader查找使用类时,将首先遍历providers中的实现。全部遍历后,继续遍历configs配置中未加载的部分,进行加载,最终将所有使用的类,一点点载入



发布于: 2020 年 05 月 24 日 阅读数: 26
用户头像

Skysper

关注

还未添加个人签名 2016.03.29 加入

还未添加个人简介

评论

发布
暂无评论
Service Provider Interface介绍