深入探討Java?SPI機(jī)制及其應(yīng)用場(chǎng)景
一、什么是SPI
SPI全稱Service Provider Interface,是Java提供的一種服務(wù)發(fā)現(xiàn)機(jī)制。實(shí)現(xiàn)服務(wù)接口和服務(wù)實(shí)現(xiàn)的解耦。
Java SPI 實(shí)際上是“基于接口的編程+策略模式+配置文件”組合實(shí)現(xiàn)的動(dòng)態(tài)加載機(jī)制,實(shí)現(xiàn)不修改任何代碼的情況下切換不同的實(shí)現(xiàn)。
二、使用場(chǎng)景
很多開源第三方j(luò)ar包都有基于SPI的實(shí)現(xiàn),在jar包META-INF/services中都有相關(guān)配置文件。
如下幾個(gè)常見(jiàn)的場(chǎng)景:
1)JDBC加載不同類型的數(shù)據(jù)庫(kù)驅(qū)動(dòng)
2)Slf4j日志框架
3)Dubbo框架
三、使用步驟示例
假設(shè)有個(gè)上傳附件的場(chǎng)景,可以上傳到不同的云儲(chǔ)存(如阿里云OSS,亞馬遜S3),那么基于Java SPI機(jī)制的實(shí)現(xiàn),我們應(yīng)該做如下步驟:
步驟1、創(chuàng)建4個(gè)工程
SPI的核心就是實(shí)現(xiàn)服務(wù)接口和服務(wù)實(shí)現(xiàn)的解耦,所以我們不能將接口和實(shí)現(xiàn)放在一個(gè)工程里面。
- spi-file-upload,在這定義附件上傳接口
IFileUpload
- spi-file-upload-oss,實(shí)現(xiàn)附件上傳到oss,
FileUploadOss
實(shí)現(xiàn)接口IFileUpload
- spi-file-upload-s3,實(shí)現(xiàn)附件上傳到s3,
FileUploadS3
實(shí)現(xiàn)接口IFileUpload
- spi-file-upload-test,通過(guò)ServiceLoader加載接口實(shí)現(xiàn),進(jìn)行測(cè)試
步驟2、 在工程spi-file-upload創(chuàng)建接口IFileUpload
接口代碼示例
package com.hj.test.file.oss; /** * 文件上傳接口 */ public interface IFileUpload { void upload(String fileName); }
步驟3、分別創(chuàng)建接口實(shí)現(xiàn)類FileUploadOss、FileUploadS3
1)FileUploadOss
在工程的 spi-file-upload-oss 的 resources目錄下創(chuàng)建目錄META-INF/services,并在該目錄中創(chuàng)建以接口IFileUpload全路徑命名的文件(com.hj.test.file.IFileUpload),文件內(nèi)容是接口實(shí)現(xiàn)類 com.hj.test.file.oss.FileUploadOss
package com.hj.test.file.oss; import com.hj.test.file.IFileUpload; public class FileUploadOss implements IFileUpload { @Override public void upload(String fileName) { System.out.println("上傳到阿里云OSS..." + fileName); } }
2)FileUploadS3
在工程的 spi-file-upload-s3 的 resources目錄下創(chuàng)建目錄META-INF/services,并在該目錄中創(chuàng)建以接口IFileUpload全路徑命名的文件(com.hj.test.file.IFileUpload),文件內(nèi)容是接口實(shí)現(xiàn)類 com.hj.test.file.s3.FileUploadS3
package com.hj.test.file.s3; import com.hj.test.file.IFileUpload; public class FileUploadS3 implements IFileUpload { @Override public void upload(String fileName) { System.out.println("上傳到亞馬遜s3..." + fileName); } }
步驟4、在工程spi-file-upload-test中創(chuàng)建測(cè)試調(diào)用類
1)在pom.xml中引入3個(gè)依賴工程
<dependency> <groupId>com.hj</groupId> <artifactId>spi-file-upload</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.hj</groupId> <artifactId>spi-file-upload-oss</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com.hj</groupId> <artifactId>spi-file-upload-s3</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
2)測(cè)試實(shí)現(xiàn)
package com.hj.test.file.test; import com.hj.test.file.IFileUpload; import java.util.Iterator; import java.util.ServiceLoader; public class FileTest{ public static void main(String[] args) { ServiceLoader<IFileUpload> loader = ServiceLoader.load(IFileUpload.class); for(Iterator<IFileUpload> it = loader.iterator(); it.hasNext();){ IFileUpload file = it.next(); file.upload("測(cè)試文件上傳"); } } }
控制臺(tái)輸出
上傳到阿里云OSS...測(cè)試文件上傳
上傳到亞馬遜s3...測(cè)試文件上傳
如果哪天不想要傳到s3,只需要把jar包依賴去掉就可以,無(wú)需改代碼
四、原理解析
1、SPI的核心就是ServiceLoader.load()方法
總結(jié)如下:
- 調(diào)用
ServiceLoader.load()
,創(chuàng)建一個(gè)ServiceLoader
實(shí)例對(duì)象 - 創(chuàng)建
LazyIterator
實(shí)例對(duì)象lookupIterator
- 通過(guò)
lookupIterator.hasNextService()
方法讀取固定目錄META-INF/services/
下面service全限定名文件,放在Enumeration
對(duì)象configs
中 - 解析configs得到迭代器對(duì)象
Iterator<String> pending
- 通過(guò)
lookupIterator.nextService()
方法初始化讀取到的實(shí)現(xiàn)類,通過(guò)Class.forName()
初始化
從上面的步驟可以總結(jié)以下幾點(diǎn)
- 實(shí)現(xiàn)類工程必須創(chuàng)建定目錄
META-INF/services/
,并創(chuàng)建service全限定名文件,文件內(nèi)容是實(shí)現(xiàn)類全限定名 - 實(shí)現(xiàn)類必須有一個(gè)無(wú)參構(gòu)造函數(shù)
2、ServiceLoader核心代碼介紹
public final class ServiceLoader<S> implements Iterable<S> { private static final String PREFIX = "META-INF/services/"; // The class or interface representing the service being loaded private final Class<S> service; // The class loader used to locate, load, and instantiate providers private final ClassLoader loader; // The access control context taken when the ServiceLoader is created private final AccessControlContext acc; // Cached providers, in instantiation order private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // The current lazy-lookup iterator private LazyIterator lookupIterator;
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); }
public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); } private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); }
通過(guò)方法iterator()生成迭代器,內(nèi)部調(diào)用LazyIterator實(shí)例對(duì)象
public Iterator<S> iterator() { return new Iterator<S>() { Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); public boolean hasNext() { if (knownProviders.hasNext()) return true; return lookupIterator.hasNext(); } public S next() { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; }
內(nèi)部類LazyIterator,讀取配置文件META-INF/services/
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() { if (nextName != null) { return true; } if (configs == null) { try { String fullName = PREFIX + service.getName(); 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()) { 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 { S p = service.cast(c.newInstance()); 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(); } }
到此這篇關(guān)于深入探討Java SPI機(jī)制及其應(yīng)用場(chǎng)景的文章就介紹到這了,更多相關(guān)Java SPI機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決restlet client報(bào)錯(cuò)No response.Is the cer
這篇文章主要介紹了解決restlet client報(bào)錯(cuò)No response.Is the certificate valid? Click here to check.問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01Springboot+AOP實(shí)現(xiàn)返回?cái)?shù)據(jù)提示語(yǔ)國(guó)際化的示例代碼
這篇文章主要介紹了Springboot+AOP實(shí)現(xiàn)返回?cái)?shù)據(jù)提示語(yǔ)國(guó)際化的示例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-07-07Windows下使用Graalvm將Springboot應(yīng)用編譯成exe大大提高啟動(dòng)和運(yùn)行效率(推薦)
這篇文章主要介紹了Windows下使用Graalvm將Springboot應(yīng)用編譯成exe大大提高啟動(dòng)和運(yùn)行效率,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02SpringCloud Ribbon負(fù)載均衡實(shí)例解析
這篇文章主要介紹了SpringCloud Ribbon負(fù)載均衡實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Spring Cloud中Eureka開啟密碼認(rèn)證的實(shí)例
這篇文章主要介紹了Spring Cloud中Eureka開啟密碼認(rèn)證的實(shí)例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05關(guān)于Java中使用jdbc連接數(shù)據(jù)庫(kù)中文出現(xiàn)亂碼的問(wèn)題
這篇文章主要介紹了關(guān)于Java中使用jdbc連接數(shù)據(jù)庫(kù)中文出現(xiàn)亂碼的問(wèn)題,默認(rèn)的編碼和數(shù)據(jù)庫(kù)表中的數(shù)據(jù)使用的編碼是不一致的,如果是中文,那么在數(shù)據(jù)庫(kù)中執(zhí)行時(shí)已經(jīng)是亂碼了,需要的朋友可以參考下2023-04-04SpringCloud注冊(cè)中心之consul詳細(xì)講解使用方法
Consul是一款由HashiCorp公司開源的,用于服務(wù)治理的軟件,Spring Cloud Consul對(duì)其進(jìn)行了封裝,這篇文章主要介紹了springcloud組件consul服務(wù)治理,需要的朋友可以參考下2022-11-11