本文主要对JDK的SPI机制进行说明,以及相关的源码示例
1. 什么是JDK SPI 机制
- 在项目的资源文件目录下创建
META-INF/services/
目录 - 在
META-INF/services/
目录下,创建名称为接口限定名的文件, 如com.m.code.HelloService
- 在以接口限定名的文件中将实现类的限定名称分行显示即可,如:
com.m.code.service.impl.HelloServiceImpl
com.m.code.service.impl.SimpleHelloServiceImpl
- 通过
ServiceLoader.load
方法即可加载指定接口的实现类
2. 源码分析
主要核心类是
java.util.ServiceLoader
- 在类里可以看到加载的目录
private static final String PREFIX = "META-INF/services/";
- 调用load方法
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
- 实际会调用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();
}
- 核心方法
private S nextService() {
// 判断是否还有service
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 创建 Class
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");
}
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);
}
throw new Error(); // This cannot happen
}
3. 实践
3.1 创建接口HelloService
/**
* HelloService 接口
*/
public interface HelloService {
void say();
}
3.2 创建接口HelloService的实现类
/**
* HelloService 接口实现类
*/
public class HelloServiceImpl implements HelloService {
@Override
public void say() {
System.out.println("Hello!!!");
}
}
/**
* HelloService 接口实现类
* 简单实现
*/
public class SimpleHelloServiceImpl implements HelloService {
@Override
public void say() {
System.out.println("simple Hello!");
}
}
3.3 在项目的resources下面新增META-INF/services/
目录
并在目录下面创建一个名称为接口限定名的文件com.m.code.service.HelloService
3.4 在文件中填写对应接口的接口实现类的限定名称
com.m.code.service.impl.HelloServiceImpl
com.m.code.service.impl.SimpleHelloServiceImpl
3.5 在方法中测试
/**
* HelloService测试
* 测试JDK SPI
*/
public class Main {
public static void main(String[] args) {
// 通过ServiceLoader加装HelloService的实现类
ServiceLoader<HelloService> serviceLoader = ServiceLoader.load(HelloService.class);
// 遍历打印一下结果
for (HelloService next : serviceLoader) {
next.say();
}
}
}