您好,登錄后才能下訂單哦!
本篇內容介紹了“如何理解Java中的SPI機制”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
SPI的概念
SPI在Java中的全稱為Service Provider Interface,是JDK內置的一種服務提供發現機制,是Java提供的一套用來被第三方實現或者擴展的API,它可以用來啟用框架擴展和替換組件。
JAVA SPI = 基于接口的編程+策略模式+配置文件的動態加載機制
SPI的使用場景
Java是一種面向對象語言,雖然Java8開始支持函數式編程和Stream,但是總體來說,還是面向對象的語言。在使用Java進行面向對象開發時,一般會推薦使用基于接口的編程,程序的模塊與模塊之前不會直接進行實現類的硬編碼。而在實際的開發過程中,往往一個接口會有多個實現類,各實現類要么實現的邏輯不同,要么使用的方式不同,還有的就是實現的技術不同。為了使調用方在調用接口的時候,明確的知道自己調用的是接口的哪個實現類,或者說為了實現在模塊裝配的時候不用在程序里動態指明,這就需要一種服務發現機制。Java中的SPI加載機制能夠滿足這樣的需求,它能夠自動尋找某個接口的實現類。
大量的框架使用了Java的SPI技術,如下:
(1)JDBC加載不同類型的數據庫驅動 (2)日志門面接口實現類加載,SLF4J加載不同提供商的日志實現類 (3)Spring中大量使用了SPI
對servlet3.0規范
對ServletContainerInitializer的實現
自動類型轉換Type Conversion SPI(Converter SPI、Formatter SPI)等
(4)Dubbo里面有很多個組件,每個組件在框架中都是以接口的形成抽象出來!具體的實現又分很多種,在程序執行時根據用戶的配置來按需取接口的實現
SPI的使用
當服務的提供者,提供了接口的一種實現后,需要在Jar包的META-INF/services/目錄下,創建一個以接口的名稱(包名.接口名的形式)命名的文件,在文件中配置接口的實現類(完整的包名+類名)。
當外部程序通過java.util.ServiceLoader類裝載這個接口時,就能夠通過該Jar包的META/Services/目錄里的配置文件找到具體的實現類名,裝載實例化,完成注入。同時,SPI的規范規定了接口的實現類必須有一個無參構造方法。
SPI中查找接口的實現類是通過java.util.ServiceLoader,而在java.util.ServiceLoader類中有一行代碼如下:
// 加載具體實現類信息的前綴,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目錄下 private static final String PREFIX = "META-INF/services/";
這也就是說,我們必須將接口的配置文件寫到Jar包的META/Services/目錄下。
SPI實例
這里,給出一個簡單的SPI使用實例,演示在Java程序中如何使用SPI動態加載接口的實現類。
注意:實例是基于Java8進行開發的。
1.創建Maven項目
在IDEA中創建Maven項目spi-demo,如下:
2.編輯pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <artifactId>spi-demo</artifactId> <groupId>io.binghe.spi</groupId> <packaging>jar</packaging> <version>1.0.0-SNAPSHOT</version> <modelVersion>4.0.0</modelVersion> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.0</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
3.創建類加載工具類
在io.binghe.spi.loader包下創建MyServiceLoader,MyServiceLoader類中直接調用JDK的ServiceLoader類加載Class。代碼如下所示。
package io.binghe.spi.loader; import java.util.ServiceLoader; /** * @author binghe * @version 1.0.0 * @description 類加載工具 */ public class MyServiceLoader { /** * 使用SPI機制加載所有的Class */ public static <S> ServiceLoader<S> loadAll(final Class<S> clazz) { return ServiceLoader.load(clazz); } }
4.創建接口
在io.binghe.spi.service包下創建接口MyService,作為測試接口,接口中只有一個方法,打印傳入的字符串信息。代碼如下所示:
package io.binghe.spi.service; /** * @author binghe * @version 1.0.0 * @description 定義接口 */ public interface MyService { /** * 打印信息 */ void print(String info); }
5.創建接口的實現類
(1)創建第一個實現類MyServiceA
在io.binghe.spi.service.impl包下創建MyServiceA類,實現MyService接口。代碼如下所示:
package io.binghe.spi.service.impl; import io.binghe.spi.service.MyService; /** * @author binghe * @version 1.0.0 * @description 接口的第一個實現 */ public class MyServiceA implements MyService { @Override public void print(String info) { System.out.println(MyServiceA.class.getName() + " print " + info); } }
(2)創建第二個實現類MyServiceB
在io.binghe.spi.service.impl包下創建MyServiceB類,實現MyService接口。代碼如下所示:
package io.binghe.spi.service.impl; import io.binghe.spi.service.MyService; /** * @author binghe * @version 1.0.0 * @description 接口第二個實現 */ public class MyServiceB implements MyService { @Override public void print(String info) { System.out.println(MyServiceB.class.getName() + " print " + info); } }
6.創建接口文件
在項目的src/main/resources目錄下創建META/Services/目錄,在目錄中創建io.binghe.spi.service.MyService文件,注意:文件必須是接口MyService的全名,之后將實現MyService接口的類配置到文件中,如下所示:
io.binghe.spi.service.impl.MyServiceA io.binghe.spi.service.impl.MyServiceB
7.創建測試類
在項目的io.binghe.spi.main包下創建Main類,該類為測試程序的入口類,提供一個main()方法,在main()方法中調用ServiceLoader類加載MyService接口的實現類。并通過Java8的Stream將結果打印出來,如下所示:
package io.binghe.spi.main; import io.binghe.spi.loader.MyServiceLoader; import io.binghe.spi.service.MyService; import java.util.ServiceLoader; import java.util.stream.StreamSupport; /** * @author binghe * @version 1.0.0 * @description 測試的main方法 */ public class Main { public static void main(String[] args){ ServiceLoader<MyService> loader = MyServiceLoader.loadAll(MyService.class); StreamSupport.stream(loader.spliterator(), false).forEach(s -> s.print("Hello World")); } }
8.測試實例
運行Main類中的main()方法,打印出的信息如下所示:
io.binghe.spi.service.impl.MyServiceA print Hello World io.binghe.spi.service.impl.MyServiceB print Hello World Process finished with exit code 0
通過打印信息可以看出,通過Java SPI機制正確加載出接口的實現類,并調用接口的實現方法。
源碼解析
這里,主要是對SPI的加載流程涉及到的java.util.ServiceLoader的源碼的解析。
進入java.util.ServiceLoader的源碼,可以看到ServiceLoader類實現了java.lang.Iterable接口,如下所示。
public final class ServiceLoader<S> implements Iterable<S>
說明ServiceLoader類是可以遍歷迭代的。
java.util.ServiceLoader類中定義了如下的成員變量:
// 加載具體實現類信息的前綴,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目錄下 private static final String PREFIX = "META-INF/services/"; // 需要加載的接口 private final Class<S> service; // 類加載器,用于加載以接口命名的文件中配置的接口的實現類 private final ClassLoader loader; // 創建ServiceLoader時采用的訪問控制上下文環境 private final AccessControlContext acc; // 用來緩存已經加載的接口實現類,其中,Key是接口實現類的完整類名,Value為實現類對象 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 用于延遲加載實現類的迭代器 private LazyIterator lookupIterator;
可以看到ServiceLoader類中定義了加載前綴為“META-INF/services/”,所以,接口文件必須要在項目的src/main/resources目錄下的META-INF/services/目錄下創建。
從MyServiceLoader類調用ServiceLoader.load(clazz)方法進入源碼,如下所示:
//根據類的Class對象加載指定的類,返回ServiceLoader對象 public static <S> ServiceLoader<S> load(Class<S> service) { //獲取當前線程的類加載器 ClassLoader cl = Thread.currentThread().getContextClassLoader(); //動態加載指定的類,將類加載到ServiceLoader中 return ServiceLoader.load(service, cl); }
方法中調用了ServiceLoader.load(service, cl)方法,繼續跟蹤代碼,如下所示:
//通過ClassLoader加載指定類的Class,并將返回結果封裝到ServiceLoader對象中 public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){ return new ServiceLoader<>(service, loader); }
可以看到ServiceLoader.load(service, cl)方法中,調用了ServiceLoader類的構造方法,繼續跟進代碼,如下所示:
//構造ServiceLoader對象 private ServiceLoader(Class<S> svc, ClassLoader cl) { //如果傳入的Class對象為空,則判處空指針異常 service = Objects.requireNonNull(svc, "Service interface cannot be null"); //如果傳入的ClassLoader為空,則通過ClassLoader.getSystemClassLoader()獲取,否則直接使用傳入的ClassLoader loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload();
繼續跟reload()方法,如下所示。
//重新加載 public void reload() { //清空保存加載的實現類的LinkedHashMap providers.clear(); //構造延遲加載的迭代器 lookupIterator = new LazyIterator(service, loader); }
繼續跟進懶加載迭代器的構造函數,如下所示。
private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; }
可以看到,會將需要加載的接口的Class對象和類加載器賦值給LazyIterator的成員變量。
當我們在程序中迭代獲取對象實例時,首先在成員變量providers中查找是否有緩存的實例對象。如果存在則直接返回,否則調用lookupIterator延遲加載迭代器進行加載。
迭代器進行邏輯判斷的代碼如下所示:
//迭代ServiceLoader的方法 public Iterator<S> iterator() { return new Iterator<S>() { //獲取保存實現類的LinkedHashMap<String,S>的迭代器 Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); //判斷是否有下一個元素 public boolean hasNext() { //如果knownProviders存在元素,則直接返回true if (knownProviders.hasNext()) return true; //返回延遲加載器是否存在元素 return lookupIterator.hasNext(); } //獲取下一個元素 public S next() { //如果knownProviders存在元素,則直接獲取 if (knownProviders.hasNext()) return knownProviders.next().getValue(); //獲取延遲迭代器lookupIterator中的元素 return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; }
LazyIterator加載類的流程如下代碼所示
//判斷是否擁有下一個實例 private boolean hasNextService() { //如果擁有下一個實例,直接返回true if (nextName != null) { return true; } //如果實現類的全名為null if (configs == null) { try { //獲取全文件名,文件相對路徑+文件名稱(包名+接口名) String fullName = PREFIX + service.getName(); //類加載器為空,則通過ClassLoader.getSystemResources()方法獲取 if (loader == null) configs = ClassLoader.getSystemResources(fullName); else //類加載器不為空,則直接通過類加載器獲取 configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { //如果configs中沒有更過的元素,則直接返回false if (!configs.hasMoreElements()) { return false; } //解析包結構 pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; } private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { //加載類對象 c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { //通過c.newInstance()生成對象實例 S p = service.cast(c.newInstance()); //將生成的對象實例保存到緩存中(LinkedHashMap<String,S>) providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen } public boolean hasNext() { if (acc == null) { return hasNextService(); } else { PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { public Boolean run() { return hasNextService(); } }; return AccessController.doPrivileged(action, acc); } } public S next() { if (acc == null) { return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } }
最后,給出整個java.util.ServiceLoader的類,如下所示:
package java.util; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedAction; public final class ServiceLoader<S> implements Iterable<S> { // 加載具體實現類信息的前綴,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目錄下 private static final String PREFIX = "META-INF/services/"; // 需要加載的接口 private final Class<S> service; // 類加載器,用于加載以接口命名的文件中配置的接口的實現類 private final ClassLoader loader; // 創建ServiceLoader時采用的訪問控制上下文環境 private final AccessControlContext acc; // 用來緩存已經加載的接口實現類,其中,Key是接口實現類的完整類名,Value為實現類對象 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 用于延遲加載實現類的迭代器 private LazyIterator lookupIterator; //重新加載 public void reload() { //清空保存加載的實現類的LinkedHashMap providers.clear(); //構造延遲加載的迭代器 lookupIterator = new LazyIterator(service, loader); } //構造ServiceLoader對象 private ServiceLoader(Class<S> svc, ClassLoader cl) { //如果傳入的Class對象為空,則判處空指針異常 service = Objects.requireNonNull(svc, "Service interface cannot be null"); //如果傳入的ClassLoader為空,則通過ClassLoader.getSystemClassLoader()獲取,否則直接使用傳入的ClassLoader loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); } private static void fail(Class<?> service, String msg, Throwable cause) throws ServiceConfigurationError { throw new ServiceConfigurationError(service.getName() + ": " + msg, cause); } private static void fail(Class<?> service, String msg) throws ServiceConfigurationError { throw new ServiceConfigurationError(service.getName() + ": " + msg); } private static void fail(Class<?> service, URL u, int line, String msg) throws ServiceConfigurationError { fail(service, u + ":" + line + ": " + msg); } // Parse a single line from the given configuration file, adding the name // on the line to the names list. // private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError { String ln = r.readLine(); if (ln == null) { return -1; } int ci = ln.indexOf('#'); if (ci >= 0) ln = ln.substring(0, ci); ln = ln.trim(); int n = ln.length(); if (n != 0) { if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) fail(service, u, lc, "Illegal configuration-file syntax"); int cp = ln.codePointAt(0); if (!Character.isJavaIdentifierStart(cp)) fail(service, u, lc, "Illegal provider-class name: " + ln); for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { cp = ln.codePointAt(i); if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) fail(service, u, lc, "Illegal provider-class name: " + ln); } if (!providers.containsKey(ln) && !names.contains(ln)) names.add(ln); } return lc + 1; } private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError { InputStream in = null; BufferedReader r = null; ArrayList<String> names = new ArrayList<>(); try { in = u.openStream(); r = new BufferedReader(new InputStreamReader(in, "utf-8")); int lc = 1; while ((lc = parseLine(service, u, r, lc, names)) >= 0); } catch (IOException x) { fail(service, "Error reading configuration file", x); } finally { try { if (r != null) r.close(); if (in != null) in.close(); } catch (IOException y) { fail(service, "Error closing configuration file", y); } } return names.iterator(); } // Private inner class implementing fully-lazy provider lookupload private class LazyIterator implements Iterator<S> { Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> pending = null; String nextName = null; private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; } //判斷是否擁有下一個實例 private boolean hasNextService() { //如果擁有下一個實例,直接返回true if (nextName != null) { return true; } //如果實現類的全名為null if (configs == null) { try { //獲取全文件名,文件相對路徑+文件名稱(包名+接口名) String fullName = PREFIX + service.getName(); //類加載器為空,則通過ClassLoader.getSystemResources()方法獲取 if (loader == null) configs = ClassLoader.getSystemResources(fullName); else //類加載器不為空,則直接通過類加載器獲取 configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { //如果configs中沒有更過的元素,則直接返回false if (!configs.hasMoreElements()) { return false; } //解析包結構 pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; } private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { //加載類對象 c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { //通過c.newInstance()生成對象實例 S p = service.cast(c.newInstance()); //將生成的對象實例保存到緩存中(LinkedHashMap<String,S>) providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen } public boolean hasNext() { if (acc == null) { return hasNextService(); } else { PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { public Boolean run() { return hasNextService(); } }; return AccessController.doPrivileged(action, acc); } } public S next() { if (acc == null) { return nextService(); } else { PrivilegedAction<S> action = new PrivilegedAction<S>() { public S run() { return nextService(); } }; return AccessController.doPrivileged(action, acc); } } public void remove() { throw new UnsupportedOperationException(); } } //迭代ServiceLoader的方法 public Iterator<S> iterator() { return new Iterator<S>() { //獲取保存實現類的LinkedHashMap<String,S>的迭代器 Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); //判斷是否有下一個元素 public boolean hasNext() { //如果knownProviders存在元素,則直接返回true if (knownProviders.hasNext()) return true; //返回延遲加載器是否存在元素 return lookupIterator.hasNext(); } //獲取下一個元素 public S next() { //如果knownProviders存在元素,則直接獲取 if (knownProviders.hasNext()) return knownProviders.next().getValue(); //獲取延遲迭代器lookupIterator中的元素 return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; } //通過ClassLoader加載指定類的Class,并將返回結果封裝到ServiceLoader對象中 public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); } //根據類的Class對象加載指定的類,返回ServiceLoader對象 public static <S> ServiceLoader<S> load(Class<S> service) { //獲取當前線程的類加載器 ClassLoader cl = Thread.currentThread().getContextClassLoader(); //動態加載指定的類,將類加載到ServiceLoader中 return ServiceLoader.load(service, cl); } public static <S> ServiceLoader<S> loadInstalled(Class<S> service) { ClassLoader cl = ClassLoader.getSystemClassLoader(); ClassLoader prev = null; while (cl != null) { prev = cl; cl = cl.getParent(); } return ServiceLoader.load(service, prev); } /** * Returns a string describing this service. * * @return A descriptive string */ public String toString() { return "java.util.ServiceLoader[" + service.getName() + "]"; } }
SPI總結
最后,對Java提供的SPI機制進行簡單的總結。
優點:
能夠實現項目解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離,而不是耦合在一起。應用程序可以根據實際業務情況啟用框架擴展或替換框架組件。
缺點:
多個并發多線程使用ServiceLoader類的實例是不安全的
雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現類全部加載并實例化一遍。
“如何理解Java中的SPI機制”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。