網站首頁 編程語言 正文
Spi機制介紹
SPI 全稱是 Service Provider Interface,是一種將服務接口與服務實現分離以達到解耦、可以提升程序可擴展性的機制。嘿嘿,看到這個概念很多人肯定是一頭霧水了,沒事,我們直接就可以簡單理解為是一種反射機制,即我們不需要知道具體的實現方,只要定義好接口,我們就能夠在運行時找到一個實現接口的類,我們具體看一下官方定義。
舉個例子
加入我是一個庫設計者,我希望把一個接口暴露給使用者實現具體的邏輯,那么我肯定不能夠寫死實現類對吧,不然我們怎么擴展嘛!比如我們有以下接口
package com.example.newtestproject
interface TestSpi {
fun getSpi();
}
如果我在使用的過程中,想不關心具體的實現類/又或者想兼容多個實現,那么怎么辦呢?(比如日常開發的gradle,如果我想兼容多個agp版本在自己庫的運行,一個個寫死肯定是一個非常糟糕的實現,如果出了新版本,那么我們還要更改代碼),我們能不能只定義一個規范,即上面的TestSpi就可以不關心以后的擴展呢?很簡單,我們只需要在resource/META-INF/services目錄下定義一個以該接口為名稱的文件,文件內容是具體的接口實現類即可,如圖
內容是實現類的全名稱,假如我們有以下實現類
class TheTestSpi:TestSpi {
override fun getSpi() {
Log.i("hello","i am the interface implementation from TheTestSpi")
}
}
那么文件只需要寫入com.example.newtestproject.TheTestSpi即可。
在我們想要用到TestSpi的功能的時候,就可以通過以下方式進行使用,從而不用關心具體的實現,達到了解耦合的目的
// spi test
val load = ServiceLoader.load(TestSpi::class.java)
load.forEach {
it.getSpi()
if(it is TheTestSpi){
Log.i("hello","theTestSpi")
}
}
ServiceLoader.load
從上面我們可以看到,最關鍵的是調用了ServiceLoader.load方法,這個就是Spi具體的實現了,本質是什么呢?相信都能夠猜到了,其實就是反射,我們來跟一下源代碼
public static <S> ServiceLoader<S> load(Class<S> service) {
// 獲取了一個當前類的classloader,做好了準備
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
緊接著就會調用到
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;
}
我們可以看到,執行的時候有這么一句String fullName = PREFIX + service.getName();,很容易想到,如果我們要反射的話,是不是需要類全稱,PREFIX 其實就是
private static final String PREFIX = "META-INF/services/";
可以看到,之所以我們需要在resource下定一個文件路徑是META-INF/services,是因為在ServiceLoader中定義好了,所以ServiceLoader會按照約定的路徑,去該文件夾下查找service.getName()(TestSpi)的實現類,最后通過
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,
// Android-changed: Let the ServiceConfigurationError have a cause.
"Provider " + cn + " not found", x);
// "Provider " + cn + " not found");
}
.....
Class.forName(cn, false, loader) 去進行了類查找操作,因為我們通過foreach去遍歷,其實就是通過迭代器去獲取了每個實例,所以才能夠調用到了實現類TheTestSpi的getSpi() 方法。
在Android中的應用
SPI機制在很多庫的設計上都有應用,比如Coroutine(kotlin協程庫)就有用到,比如我們經常用到的 MainCoroutineDispatcher,如果我們希望一個任務運行在主線程,在Android就可以通過handler的方式去向主線程post一個消息,那如果在其他環境呢?
kotlin不僅僅是想在android的世界立足,還有很多比如如果在native環境呢?在服務器環境呢?多平臺環境呢(如KMM),那就不一定有Handler這個概念對不對!但是都有一個主線程的概念,所以Coroutine把這部分就通過SPI的方式去實現了,如
定義了這個接口 MainDispatcherFactory::class.java 用于給具體環境的主線程實現類進行實現,具體的實現類就是
internal class AndroidDispatcherFactory : MainDispatcherFactory {
override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
HandlerContext(Looper.getMainLooper().asHandler(async = true))
override fun hintOnError(): String? = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"
override val loadPriority: Int
get() = Int.MAX_VALUE / 2
}
可以看到,在Android中就通過了Handler去實現,最后我們可以在源碼中看到,SPI相關的注冊信息
總結
通過SPI技術去實現的解耦合工作的出色工程還有很多很多,比如我們用的APT,還有didi開源的Booster,都有用到這方面的知識。
原文鏈接:https://juejin.cn/post/7109666495566708766
相關推薦
- 2022-05-24 C#?連接本地數據庫的實現示例_C#教程
- 2022-12-10 C語言中的結構體快排算法_C 語言
- 2022-11-06 React如何接收excel文件下載導出功能封裝_React
- 2022-05-01 Python類的定義和使用詳情_python
- 2023-05-15 golang判斷結構體為空的問題_Golang
- 2022-09-24 React報錯之Object?is?possibly?null的問題及解決方法_React
- 2021-12-08 .net?core?api接口JWT方式認證Token_實用技巧
- 2022-11-07 python中openpyxl庫用法詳解_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支