1. Java SPI 概述
SPI(Service Provider Interface)即服务提供接口,是JDK内置的一种服务提供发现机制。简单来说,它就是一种动态替换发现机制。在程序启动时加载配置文件,在程序调用的时候才通过反射去实例化具体的实现类。Java提供了很多默认的SPI,允许第三方为这些接口提供实现。常见的SPI有JDBC、JCE、JNDI、JAXP和JBI等。
SPI的接口是Java核心库的一部分所以是由BootstrapClassloader(引导类加载器)来加载的,SPI的实现类是由AppClassLoader(应用类加载器)来加载的。因此BootstrapClassloader是无法找到SPI的实现类的(双亲委派模型),顾需要引入线程上下文类加载(后面源码分析可以看到)来破坏类加载的双亲委派模型,使得程序可以进行逆向类加载。
SPI和API(Application Programming Interface)的区别可以简单理解为:API是由开发人员制定接口并完成对接口的实现,暴露接口供外部人员调用;SPI则是调用方来制定接口规范,外部扩展人员来提供具体实现。
2. Java SPI 示例
2.1 创建示例maven工程
2.2 编写SPI接口及实现(一般是外部使用人员扩展实现)
package com.qqxhb.spi.java;
public interface IAnimal {
void say();
}
package com.qqxhb.spi.java;
public class Cat implements IAnimal {
@Override
public void say() {
System.out.println("猫叫========");
}
}
package com.qqxhb.spi.java;
public class Dog implements IAnimal {
@Override
public void say() {
System.out.println("狗吠========");
}
}
2.3 编写接口配置文件
在classpath下创建META-INF/services文件夹,在文件夹下创建具体接口全限定名文件,在文件中配置接口的具体实现(类全限定名)。本例com.qqxhb.spi.java.IAnimal文件内容如下:
com.qqxhb.spi.java.Cat
com.qqxhb.spi.java.Dog
2.4 使用ServiceLoader加载实现类并调用
package com.qqxhb.spi.java;
import java.util.ServiceLoader;
public class JavaSPI {
public static void main(String[] args) {
ServiceLoader<IAnimal> serviceLoader = ServiceLoader.load(IAnimal.class);
serviceLoader.forEach(IAnimal::say);
}
}
调用成功,结果在控制台打印:
猫叫========
狗吠========
3. Java SPI 加载源码分析
- 根据上面的实例我们查看java.util.ServiceLoader.load(Class)方法源码
public static <S> ServiceLoader<S> load(Class<S> service) {
//获取线程上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
//调用重载方法
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{ //重载方法直接创建了一个ServiceLoader实例
return new ServiceLoader<>(service, loader);
}
- 继续上面的源码,查看ServiceLoader的属性和构造函数,省略部分源码
public final class ServiceLoader<S>
implements Iterable<S>
{
//服务路径前缀,写死的因此自定义的配置SPI服务必须放到这个路径下
private static final String PREFIX = "META-INF/services/";
// 表示正在加载的服务的类或接口
private final Class<S> service;
// 用于定位、加载和实例化提供程序的类加载器
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// 创建ServiceLoader时获取的访问控制上下文
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 延迟加载迭代器
private LazyIterator lookupIterator;
/**
* 清空服务提供者(即实现类实例)
* 初始化延迟加载迭代器
*/
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
//私有化构造函数,简单对属性赋值,并调用reload方法
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();
}
}
3.结合上面代码可以看到load方法最后就是创建了一个 java.util.ServiceLoader.LazyIterator (ServiceLoader的内部类)实例,并没有发现加载配置文件和服务实现,因此ServiceLoader.load方法并没有做真正的加载操作,主要逻辑都在LazyIterator的hasNextService和nextService方法中
// 迭代器的hasNext()主要调用这个方法实现的,这个方法也负责加载配置文件
private boolean hasNextService() {
// nextName是下一个服务实现类的全限定名,存在则直接返回他true
if (nextName != null) {
return true;
}
// 配置为空则会去加载配置文件
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);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
//parse方法会调用parseLine方法,就是简单的按行加载配置文件内容
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
// 迭代器next方法调用本方法实现,负责反射实例化实现类
private S nextService() {
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 {
//反射创建实例,并存放到providers 中
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
}
示例源码:https://github.com/qqxhb/dubbo-demo/tree/master/dubbo-source-code-demo Dubbo SPI内容请参考下篇博客:一篇短文搞定Dubbo SPI 源码及示例