一、SPI是什么?
SPI全稱為Service Provider Interface,是一種服務發現機制。SPI的本質是將接口的全限定類名配置在文件中,並由服務加載器 ServiceLoader 讀取配置文件,加載實現類。這樣可以再運行的時候,動態的替換接口的實現類。我們可以通過SPI的這種機制為我們的程序提供拓展功能。常見的SPI包括JDBC、日志門面接口、Spring、SpringBook相關的starter組件、Dubbo、JNDI等
二、關於SPI的一些概念
服務是一組公共的接口和類(通常是抽象類),提供對某些特定應用程序功能或特性。
服務服務提供者接口,服務定義的一組公共接口和抽象類。SPI 定義了可用於您的應用程序的類和方法。
服務提供者是服務的特定實現,比如說一個APP上需要支付,日常生活中常見的支付方式有支付寶支付和微信支付,這兩種支付方式都是支付的服務提供者。
多個相同的服務,為同一個相同的服務提供多個程序,系統可以任意選擇其中一個服務提供者,用戶可以選擇已安裝的程序的一個。
服務提供者以特殊格式的 JAR 文件提供他們的新服務,通過位於資源目錄 META-INF/services 中的提供文件標志服務提供者,配置文件的名稱是服務提供者的全限定類名,其中名稱的每個組成部分用句點 (.
) 分隔。該文件必須采用 UTF-8 編碼。此外,還可以通過以數字符號 (#)開頭的注釋行來在文件中包含注釋。然后服務接口的實現類的jar包要放在主程序的Classpath下,這樣服務啟動時,主程序就會用ServiceLoader動態加載實現模塊,掃描所有jar包中的約定好的類名,調用Class.forname加載。
三、原理分析
服務加載器 ServiceLoader 的源碼的成員變量。
1 public final class ServiceLoader<S> 2 implements Iterable<S> 3 { 4 5 private static final String PREFIX = "META-INF/services/"; 6 7 // 表示正在加載的服務的類或接口 8 private final Class<S> service; 9 10 // 用於定位、加載和實例化提供者的類加載器 11 private final ClassLoader loader; 12 13 // 創建 ServiceLoader 時獲取的訪問控制上下文 14 private final AccessControlContext acc; 15 16 // 緩存提供程序,按實例化順序 17 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); 18 19 // 當前的懶加載查找迭代器 20 private LazyIterator lookupIterator; 21 ... ... 22 }
ServiceLoader的私有構造方法,服務的接口,類加載器。
1 private ServiceLoader(Class<S> svc, ClassLoader cl) { 2 service = Objects.requireNonNull(svc, "Service interface cannot be null"); 3 loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; 4 acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; 5 reload(); 6 }
ServiceLoader的 load 方法,使用了單例模式?
1 public static <S> ServiceLoader<S> load(Class<S> service, 2 ClassLoader loader) 3 { 4 return new ServiceLoader<>(service, loader); 5 }
reload 方法,清除次加載程序的所有緩存,重新加載所有的提供程序,並且實例了一個懶加載查找迭代器,等到主程序需要加載提供者程序時,才會使用類加載器去查找所有jar包的服務提供者的class,實例化服服務提供程序並緩存起來。
1 public void reload() { 2 providers.clear(); 3 lookupIterator = new LazyIterator(service, loader); 4 }
ServiceLoader的私有內部類,用來懶加載服務提供者程序。

1 private class LazyIterator 2 implements Iterator<S> 3 { 4 5 Class<S> service; 6 ClassLoader loader; 7 Enumeration<URL> configs = null; 8 Iterator<String> pending = null; 9 String nextName = null; 10 11 private LazyIterator(Class<S> service, ClassLoader loader) { 12 this.service = service; 13 this.loader = loader; 14 } 15 16 private boolean hasNextService() { 17 if (nextName != null) { 18 return true; 19 } 20 if (configs == null) { 21 try { 22 String fullName = PREFIX + service.getName(); 23 if (loader == null) 24 configs = ClassLoader.getSystemResources(fullName); 25 else 26 configs = loader.getResources(fullName); 27 } catch (IOException x) { 28 fail(service, "Error locating configuration files", x); 29 } 30 } 31 while ((pending == null) || !pending.hasNext()) { 32 if (!configs.hasMoreElements()) { 33 return false; 34 } 35 pending = parse(service, configs.nextElement()); 36 } 37 nextName = pending.next(); 38 return true; 39 } 40 41 private S nextService() { 42 if (!hasNextService()) 43 throw new NoSuchElementException(); 44 String cn = nextName; 45 nextName = null; 46 Class<?> c = null; 47 try { 48 c = Class.forName(cn, false, loader); 49 } catch (ClassNotFoundException x) { 50 fail(service, 51 "Provider " + cn + " not found"); 52 } 53 if (!service.isAssignableFrom(c)) { 54 fail(service, 55 "Provider " + cn + " not a subtype"); 56 } 57 try { 58 S p = service.cast(c.newInstance()); 59 providers.put(cn, p); 60 return p; 61 } catch (Throwable x) { 62 fail(service, 63 "Provider " + cn + " could not be instantiated", 64 x); 65 } 66 throw new Error(); // This cannot happen 67 } 68 69 public boolean hasNext() { 70 if (acc == null) { 71 return hasNextService(); 72 } else { 73 PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { 74 public Boolean run() { return hasNextService(); } 75 }; 76 return AccessController.doPrivileged(action, acc); 77 } 78 } 79 80 public S next() { 81 if (acc == null) { 82 return nextService(); 83 } else { 84 PrivilegedAction<S> action = new PrivilegedAction<S>() { 85 public S run() { return nextService(); } 86 }; 87 return AccessController.doPrivileged(action, acc); 88 } 89 } 90 91 public void remove() { 92 throw new UnsupportedOperationException(); 93 } 94 95 }
ServiceLoader首先會去確認是否有緩存有實例對象,有則直接從緩存中返回。沒有則會執行懶加載的方法,從使用反射方法,從ClassLoader加載並實例化服務提供者,並放入緩存。
1 public Iterator<S> iterator() { 2 return new Iterator<S>() { 3 4 Iterator<Map.Entry<String,S>> knownProviders 5 = providers.entrySet().iterator(); 6 7 public boolean hasNext() { 8 if (knownProviders.hasNext()) 9 return true; 10 return lookupIterator.hasNext(); 11 } 12 13 public S next() { 14 if (knownProviders.hasNext()) 15 return knownProviders.next().getValue(); 16 return lookupIterator.next(); 17 } 18 19 public void remove() { 20 throw new UnsupportedOperationException(); 21 } 22 23 }; 24 }
ServiceLoader可以跨jar包獲取META-INF/services目錄中的文件,讀取文件得到所有可以實例化的類的名稱。
使用反射方法,Class.forName()用於加載類對象,instance()用於實例化類。最后放入緩存providers中,返回實例的服務提供者對象。
1 private S nextService() { 2 if (!hasNextService()) 3 throw new NoSuchElementException(); 4 String cn = nextName; 5 nextName = null; 6 Class<?> c = null; 7 try { 8 c = Class.forName(cn, false, loader); 9 } catch (ClassNotFoundException x) { 10 fail(service, 11 "Provider " + cn + " not found"); 12 } 13 if (!service.isAssignableFrom(c)) { 14 fail(service, 15 "Provider " + cn + " not a subtype"); 16 } 17 try { 18 S p = service.cast(c.newInstance()); 19 providers.put(cn, p); 20 return p; 21 } catch (Throwable x) { 22 fail(service, 23 "Provider " + cn + " could not be instantiated", 24 x); 25 } 26 throw new Error(); // This cannot happen 27 }