Java SPI 源码解析及 demo 讲解
??點擊上方?好好學java?,選擇?星標?公眾號
重磅資訊、干貨,第一時間送達 今日推薦:Java實現QQ登錄和微博登錄個人原創+1博客:點擊前往,查看更多 作者:JlDang 來源:https://segmentfault.com/a/11900000221018121. SPI是什么,有什么用處
SPI全稱S而vice Provider Interface,是java提供的一套用來被第三方實現或者擴展的API,它可以用來啟用框架擴展和替換組件。
系統設計的各個抽象,一般有很多不同的實現方案,比如通過不同類型的配置文件加載配置信息,通過不同的序列化方案實現序列化。一般推薦模塊之間基于接口編程,模塊之間不對實現類進行硬編碼。一旦代碼里涉及具體的實現類,就違反了可插拔的原則。為了實現在模塊裝配的時候能不在程序里動態指明,這就需要一種服務發現機制。
SPI的核心思想就是解耦。
2.使用場景
調用者根據實際需要替換框架的實現策略。
比如常見的例子:
數據庫驅動加載類接口實現類的加載,JDBC加載不同類型數據庫的驅動
日志實現類加載
dubbo中也大量使用SPI的方式實現框架的擴展,不過它對java提供的SPI進行了封裝
3.如何使用
實例代碼
定義一組接口,并寫出多個實現類
在META-INF/services文件下,創建一個以接口全限定名命名的文件,內容為實現類的全限定名
通過ServiceLoader類進行加載
執行結果
4 源碼解析
我會將分析都寫在代碼注釋中,大家可以打開自己的源碼耐心的看一會。
接下來是重頭戲了,知道了spi怎么用,那么內部是如何實現的呢?
我們直接從ServiceLoader類的load方法看起。
/**為給定的服務類型創建一個新的服務加載程序,使用 *當前線程的{@linkplain java.lang.Thread#getContextClassLoader *上下文類裝入器}。 */ public static <S> ServiceLoader<S> load(Class<S> service) {//1.獲取當前線程的類加載ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl); }</code><code class="java">public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader) {//new一個serviceloader對象return new ServiceLoader<>(service, loader); }</code><code class="java">//構造函數 private ServiceLoader(Class<S> svc, ClassLoader cl) {//判斷入參是否為nullservice = Objects.requireNonNull(svc, "Service interface cannot be null");//2.加載器如果不存在,獲取系統類加載器,通常是applicationLoader,應用程序加載器loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;//3.獲取訪問控制器acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;reload(); }</code><code class="java">public void reload() {// 清空緩存providers.clear();// 初始化內部類,用于遍歷提供者lookupIterator = new LazyIterator(service, loader); }看到這里,相信大家對于初始化的內容有了一定了解,這里面涉及到了一些屬性,我們來總結下
private static final String PREFIX = "META-INF/services/";// 要加載的類 private final Class<S> service;// 用于加載實現類的類加載器 private final ClassLoader loader;// 訪問控制器 private final AccessControlContext acc;// 提供者的緩存 private LinkedHashMap<String,S> providers = new LinkedHashMap<>();// 一個內部類,用于遍歷實現類 private LazyIterator lookupIterator;現在我們發現重點就在于LazyIterator這個內部類上,我們獲取實現類都看這個內部類了,我們繼續來分析
private class LazyIteratorimplements 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 {//獲取META-INF/services下文件全稱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;}</code><code class="java">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(); }</code><code class="java">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 } image.png//這里是iterable循環遍歷 default void forEach(Consumer<? super T> action) {Objects.requireNonNull(action);for (T t : this) {action.accept(t);} }到這里整個鏈路就分析完成了。
感興趣的小伙伴可以按照demo,自己跑一遍,有問題歡迎提問。
總結
以上是生活随笔為你收集整理的Java SPI 源码解析及 demo 讲解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何为你的 Github 博客添砖加瓦
- 下一篇: 十分钟了解 git 那些 “不常用” 命