SPI是Service Provider Interface的简写,是JDK内置的一种服务提供发现机制,是一种扩展机制。常见的框架使用SPI机制方式:
3、Java SPI的源码分析
首先看ServiceLoader类源码
public final class ServiceLoader implements Iteriable{
private static final String PREFIX = "META-INF/services/";
private final class service;
private final ClassLoader loader;
private final AccessControlContext acc;
private LinkedHashMap providers = new LinkedHashMap<>();
private LazyIterator lookupIterator;
private ServiceLoader(Class svc, ClassLoader cl){
service = Objects.requireNonNull(svc, "");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public static ServiceLoader load(Class svc){
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(srv, cl);
}
public static ServiceLoader load(Class svc, ClassLoader cl){
return new ServiceLoader(svc, cl);
}
public void reload(){
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
private static class LazyIterator implements Iterator{
Class service;
ClassLoader loader;
Enumeration configs = null;
Iterator pending = null;
String nextName = null;
private LazyIterator(Class services, ClassLoader loader){
this.service = service;
this.loader = loader;
}
public boolean hasNext(){....}
public S next(){...}
public void remove(){...}
}
}
接看reload 方法,其中使用到了LazyIterator对象,该对象的作用是在使用到定义的SPI类时才会去加载接口实现。
public boolean hasNext(){
if(acc == null){
return hasNextService();
}else{
PrivilegedAction action = new PrivilegedAction<>(){
public Boolean run(){return hasNextService();}
}
return AccessController.doPrivileged(action, acc);
}
}
public boolea 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);
}
}...
}
while((pending == null)|| !pending.hasNext()){
if(!configs.hasMoreElements()){
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
从hasNext()方法可以看到,只会去META-INF/services/目录下取出一个服务提供者实现类的全限定名赋值给nextName;这里使用的懒加载,是在用到真正的服务的时候才会去加载服务提供者。
再往下看LazyIterator的next()方法,
public S next(){
if(acc == null){
return nextService();
}else{
...
}
}
public S nextService(){
if(!hasNextService()){
throw new NoSuchElementException();
}
String cn = nextName;
nextName = null;
Class> c = null;
try{
c= Class.forName(c, flase, loader);
}...
try{
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
}...
}
SPI的接口是Java核心库的一部分,是由引导类加载器(Bootstrap Classloader)来加载的,SPI的实现类是由系统类加载器(System ClassLoader)来加载的。具体的Demon有:
public interface IHello{
String getHello(String name);
}
创建目录:
- src
-main
-resources
- META-INF
- services
- com.my.test.IHello
目录内容:com.my.test.IHelloService
使用 ServiceLoader 来加载配置文件中指定的实现。
public class SPIMain {
public static void main(String[] args) {
ServiceLoader ih = ServiceLoader.load(IHello.class);
for (IHello s : ih) {
s.hello();
}
}
}
dubbo的扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来,主要实现是在ExtensionLoader类中。当接口上打了@SPI注解时,dubbo会在META-INF/dubbo/internal/接口全名文件下读取扩展点.配置文件内容为name->implementation class,可以声明多个提供者。
Java SPI的缺点:
dubbo SPI 的区别: