一、什么是SPI
SPI ,全稱為 Service Provider Interface,是一種服務發現機制。它通過在ClassPath路徑下的META-INF/services文件夾查找文件,自動加載文件里所定義的類。
這一機制為很多框架擴展提供了可能,比如在Dubbo、JDBC中都使用到了SPI機制。我們先通過一個很簡單的例子來看下它是怎么用的。
1、小例子
首先,我們需要定義一個接口,SPIService
package com.viewscenes.netsupervisor.spi; public interface SPIService { void execute(); }
然后,定義兩個實現類,沒別的意思,只輸入一句話。
package com.viewscenes.netsupervisor.spi; public class SpiImpl1 implements SPIService{ public void execute() { System.out.println("SpiImpl1.execute()"); } } ----------------------我是乖巧的分割線---------------------- package com.viewscenes.netsupervisor.spi; public class SpiImpl2 implements SPIService{ public void execute() { System.out.println("SpiImpl2.execute()"); } }
最后呢,要在ClassPath路徑下配置添加一個文件。文件名字是接口的全限定類名,內容是實現類的全限定類名,多個實現類用換行符分隔。
文件路徑如下:

內容就是實現類的全限定類名:
com.viewscenes.netsupervisor.spi.SpiImpl1
com.viewscenes.netsupervisor.spi.SpiImpl2
2、測試
然后我們就可以通過ServiceLoader.load或者Service.providers
方法拿到實現類的實例。其中,Service.providers
包位於sun.misc.Service
,而ServiceLoader.load
包位於java.util.ServiceLoader
。
public class Test { public static void main(String[] args) { Iterator<SPIService> providers = Service.providers(SPIService.class); ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class); while(providers.hasNext()) { SPIService ser = providers.next(); ser.execute(); } System.out.println("--------------------------------"); Iterator<SPIService> iterator = load.iterator(); while(iterator.hasNext()) { SPIService ser = iterator.next(); ser.execute(); } } }
兩種方式的輸出結果是一致的:
SpiImpl1.execute() SpiImpl2.execute() -------------------------------- SpiImpl1.execute() SpiImpl2.execute()
二、源碼分析
我們看到一個位於sun.misc包
,一個位於java.util包
,sun包下的源碼看不到。我們就以ServiceLoader.load為例,通過源碼看看它里面到底怎么做的。
1、ServiceLoader
首先,我們先來了解下ServiceLoader,看看它的類結構。
public final class ServiceLoader<S> implements Iterable<S> //配置文件的路徑 private static final String PREFIX = "META-INF/services/"; //加載的服務類或接口 private final Class<S> service; //已加載的服務類集合 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); //類加載器 private final ClassLoader loader; //內部類,真正加載服務類 private LazyIterator lookupIterator; }
2、Load
load方法創建了一些屬性,重要的是實例化了內部類,LazyIterator。最后返回ServiceLoader的實例。
public final class ServiceLoader<S> implements Iterable<S> 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; //先清空 providers.clear(); //實例化內部類 LazyIterator lookupIterator = new LazyIterator(service, loader); } }
3、查找實現類
查找實現類和創建實現類的過程,都在LazyIterator完成。當我們調用iterator.hasNext和iterator.next方法的時候,實際上調用的都是LazyIterator的相應方法。
public Iterator<S> iterator() { return new Iterator<S>() { public boolean hasNext() { return lookupIterator.hasNext(); } public S next() { return lookupIterator.next(); } ....... }; }
所以,我們重點關注lookupIterator.hasNext()方法,它最終會調用到hasNextService。
private class LazyIterator implements Iterator<S>{ Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> pending = null; String nextName = null; private boolean hasNextService() { //第二次調用的時候,已經解析完成了,直接返回 if (nextName != null) { return true; } if (configs == null) { //META-INF/services/ 加上接口的全限定類名,就是文件服務類的文件 //META-INF/services/com.viewscenes.netsupervisor.spi.SPIService String fullName = PREFIX + service.getName(); //將文件路徑轉成URL對象 configs = loader.getResources(fullName); } while ((pending == null) || !pending.hasNext()) { //解析URL文件對象,讀取內容,最后返回 pending = parse(service, configs.nextElement()); } //拿到第一個實現類的類名 nextName = pending.next(); return true; } }
4、創建實例
當然,調用next方法的時候,實際調用到的是,lookupIterator.nextService。它通過反射的方式,創建實現類的實例並返回。
private class LazyIterator implements Iterator<S>{ private S nextService() { //全限定類名 String cn = nextName; nextName = null; //創建類的Class對象 Class<?> c = Class.forName(cn, false, loader); //通過newInstance實例化 S p = service.cast(c.newInstance()); //放入集合,返回實例 providers.put(cn, p); return p; } }
看到這兒,我想已經很清楚了。獲取到類的實例,我們自然就可以對它為所欲為了!