Java:SPI機制
一、SPI是什麼?
SPI全稱為Service Provider Interface,是一種服務發現機制。SPI的本質是將介面的全限定類名配置在檔案中,並由服務載入器ServiceLoader讀取配置檔案,載入實現類。這樣可以再執行的時候,動態的替換介面的實現類。我們可以通過SPI的這種機制為我們的程式提供拓展功能。常見的SPI包括JDBC、日誌門面介面、Spring、SpringBook相關的starter元件、Dubbo、JNDI等
二、關於SPI的一些概念
服務是一組公共的介面和類(通常是抽象類),提供對某些特定應用程式功能或特性。
服務服務提供者介面,服務定義的一組公共介面和抽象類。
服務提供者是服務的特定實現,比如說一個APP上需要支付,日常生活中常見的支付方式有支付寶支付和微信支付,這兩種支付方式都是支付的服務提供者。
多個相同的服務,為同一個相同的服務提供多個程式,系統可以任意選擇其中一個服務提供者,使用者可以選擇已安裝的程式的一個。
服務提供者以特殊格式的 JAR 檔案提供他們的新服務,通過位於資源目錄 META-INF/services 中的提供檔案標誌服務提供者,配置檔案的名稱是服務提供者的全限定類名,其中名稱的每個組成部分用句點 (.
) 分隔。該檔案必須採用 UTF-8 編碼。此外,還可以通過以數字符號 (#
三、原理分析
服務載入器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 }View Code
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 }