1. 程式人生 > 其它 >Java:SPI機制

Java:SPI機制

一、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     }
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         }