SPI(Service Provider Interface)是JDK内置的一种服务发现机制,简单说它就是一种动态归还发现的机制,比如一个接口,想运行时动态给它添加实现,只需要添加一个实现,然后在jar包src/resources/META-INF/service
目录下创建一个接口全限定名的文件,文件中存放实现类的全限定名,允许存多个实现类全限定名
定义接口
package com.xiaofan.boot.spi;
public interface AnimalService {
void eat();
}
定义接口实现类1:
package com.xiaofan.boot.spi.impl;
import com.xiaofan.boot.spi.AnimalService;
public class CatService implements AnimalService {
@Override
public void eat() {
System.out.println("cat eat food!");
}
}
定义接口实现类2:
package com.xiaofan.boot.spi.impl;
import com.xiaofan.boot.spi.AnimalService;
public class DogService implements AnimalService {
@Override
public void eat() {
System.out.println("dog eat food!");
}
}
在src/resources/
文件夹下定义META-INF/services
资源文件夹,并定义AnimalService的全限定名资源文件:com.xiaofan.boot.spi.AnimalService
com.xiaofan.boot.spi.impl.CatService
com.xiaofan.boot.spi.impl.DogService
测试:
public class TestSpi {
public static void main(String[] args) {
ServiceLoader services = ServiceLoader.load(AnimalService.class);
Iterator iterator = services.iterator();
while(iterator.hasNext()) {
AnimalService animalService = iterator.next();
animalService.eat();
}
}
}
---
cat eat food!
dog eat food!
通过ServiceLoader可以比较方便地发现META-INF/services下的实现类,但每次都要手动添加文件,比较繁琐。AutoService可以通过注解的方式自动生成资源文件
添加AutoService依赖包,注意升级下guava包版本,否则可能会遇到包冲突问题:
com.google.auto.service
auto-service
1.0-rc5
true
在接口实现类上添加@AutoService注解
@AutoService(AnimalService.class)
public class CatService implements AnimalService {
@Override
public void eat() {
System.out.println("cat eat food!");
}
}
测试:
public class TestSpi {
public static void main(String[] args) {
ServiceLoader services = ServiceLoader.load(AnimalService.class);
Iterator iterator = services.iterator();
while(iterator.hasNext()) {
AnimalService animalService = iterator.next();
animalService.eat();
}
}
}
---
cat eat food!
查看target文件夹结构,AutoService自动生成了META-INFO/services文件夹及AnimalService的全限定名文件:
# jerry @ MacBook-Pro-6 in ~/Documents/git/test/xiaofan-boot/target on git:master x [12:31:53]
$ tree
.
|____classes
| |____com
| | |____xiaofan
| | | |____boot
| | | | |____spi
| | | | | |____AnimalService.class
| | | | | |____impl
| | | | | | |____CatService.class
| | | | | | |____DogService.class
| | | | | |____TestSpi.class
| |____log4j.properties
| |____META-INF
| | |____services
| | | |____com.xiaofan.boot.spi.AnimalService
|____generated-sources
| |____annotations
首先看下ServiceLoader的成员变量,ServiceLoader通过PREFIX指定了默认资源文件路径为:META-INF/services/;service为spi指定的接口;loader为加载实现类的ClassLoader;provider为缓存的实现类全限定名与其实例的KV对,所有实现类第一次通过反射生成实例后存储在provider中,供后续使用。
public final class ServiceLoader implements Iterable
{
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
...
}
ServiceLoader的load方法主要作用是初始化ServiceLoader的成员变量:service、loader、acc和懒加载迭代器。
private ServiceLoader(Class 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();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
ServiceLoader实现了迭代器接口,其迭代器逻辑主要如下,其主要方法hasNext()、next()都是通过懒加载迭代器lookupIterator实现。
public Iterator iterator() {
return new Iterator() {
Iterator> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
hasNext()方法核心逻辑如下,初始状态下nextName和configs都为null,程序到/META-INF/services资源文件夹下找与服务同名的资源文件,并加载其配置,将资源文件中的第一个实现类全限定名赋值给nextName
private boolean hasNextService() {
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;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
next()方法核心逻辑如下,通过反射动态生成nextName指定的实现类的对象,将其转换成泛型指定的返回类型,然后将实现类全限定名和其实例以KV形式存储到provider中。
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
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");
}
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
}