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         }

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM