一、SPI(Service Provider Interface)
在介绍ServiceLoader之前,需要先说下SPI (Service Provider Interface)这个概念。
SPI属于动态加载接口实现类的的一项技术,是JDK内置的一种服务提供发现机制,使用ServiceLoader去加载接口对应的实现,这样我们就不用关注实现类,ServiceLoader会告诉我们。官方文档描述为:为某个接口寻找服务的机制,类似IOC思想,将装配的控制权交给ServiceLoader。
使用场景
只提供服务接口,具体服务由其他组件实现,接口和具体实现分离(类似桥接),同时能够通过系统的ServiceLoader拿到这些实现类的集合,统一处理,这样在组件化中往往会带来很多便利,SPI机制可以实现不同模块之间方便的面向接口编程,拒绝了硬编码的方式,解耦效果很好。
二、使用方式
ServiceLoader的使用流程遵循:服务约定 -> 服务实现 -> 服务注册 -> 服务发现/使用。
首先约定几个概念名词,并且后文中,以这些名词行文。
概念 | 说明 | 备注 |
---|---|---|
服务 | 接口或(通常是)抽象类 | 出于加载的目的,服务由单一类型表示,即单一接口或抽象类。 (可以使用具体类,但不建议这样做。) |
服务提供者 | 服务(接口和抽象类)的具体实现。服务提供者可以以扩展形式引入,例如jar包;也可以通过将它们添加到应用程序的类路径或通过其他一些特定于平台的方式来提供。 | 给定服务提供者包含一个或多个具体类,这些类使用特定于提供者的数据和代码扩展该服务类型。 此工具强制执行的唯一要求是提供程序类必须具有零参数构造函数,以便它们可以在加载期间实例化。 |
1、服务约定
定义好接口或抽象类作为服务
2、服务实现
实现定义好的服务,由于ServiceLoader可以更方便不同组件间通信,高度解耦。所以更常见的场景是服务可能是定义在底层组件或引入jar包,在上层业务代码中具体实现。
3、服务注册
约定和实现了服务后,需要对服务进行注册,系统才能定位到该服务。注册方式是在java同级目录,创建一个resources/META-INF/services的目录,在该目录下,以服务的全限定名创建一个SPI描述文件。目录层级图如下:
有了该文件,即可将服务提供者的全限定名分行写入该文件,即完成服务注册。
PS. 注册目录路径是固定,至于为什么后,下文代码部分将会说明。
示例:
package com.example;
// 声明服务
public interface IHello {
String sayHello();
}
---------------------------------------------------------------
// 实现服务
public class Hello implements IHello{
@Override
public String sayHello(){
System.out.println("hello, world");
}
}
---------------------------------------------------------------
// 使用服务
ServiceLoader<Hello> loader = ServiceLoader.load(IHello.class);
mIterator =loader.iterator();
while(mIterator.hasNext()){
mIterator.next().sayHello();
}
三、代码逻辑
ServiceLoader成员变量说明
字段 | 类型 | 说明 |
---|---|---|
service | Class |
ServiceLoader加载的接口或抽象类 |
loader | ClassLoader | 类加载器 |
providers | LinkedHashMap<String,S> | 缓存加载的接口或抽象类(即service对象) |
lookupIterator | LazyIterator | 迭代器 |
3.1、ServiceLoader的创建
//ServiceLoader.class
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {