Java 中經常被提到的 SPI 到底是什麼?

来源:https://www.cnblogs.com/zi-you/archive/2022/12/01/16942914.html
-Advertisement-
Play Games

Java 程式員在日常工作中經常會聽到 SPI,而且很多框架都使用了 SPI 的技術,那麼問題來了,到底什麼是 SPI 呢?今天阿粉就帶大家好好瞭解一下 SPI。 SPI 概念 SPI 全稱是 Service Provider Interface,是一種 JDK 內置的動態載入實現擴展點的機制,通過 ...


Java 程式員在日常工作中經常會聽到 SPI,而且很多框架都使用了 SPI 的技術,那麼問題來了,到底什麼是 SPI 呢?今天阿粉就帶大家好好瞭解一下 SPI。

SPI 概念

SPI 全稱是 Service Provider Interface,是一種 JDK 內置的動態載入實現擴展點的機制,通過 SPI 技術我們可以動態獲取介面的實現類,不用自己來創建。

這裡提到了介面和實現類,那麼 SPI 技術上具體有哪些技術細節呢?

  1. 介面:需要有一個功能介面;
  2. 實現類:介面只是規範,具體的執行需要有實現類才行,所以不可缺少的需要有實現類;
  3. 配置文件:要實現 SPI 機制,必須有一個與介面同名的文件存放於類路徑下麵的 META-INF/services 文件夾中,並且文件中的每一行的內容都是一個實現類的全路徑;
  4. 類載入器 ServiceLoaderJDK 內置的一個類載入器,用於載入配置文件中的實現類;

舉個慄子

上面說了 SPI 的幾個概念,接下來阿粉就通過一個慄子來帶大家感受一下具體的用法。

第一步

創建一個介面,這裡我們創建一個解壓縮的介面,其中定義了壓縮和解壓的兩個方法。

package com.example.demo.spi;

/**
 * <br>
 * <b>Function:</b><br>
 * <b>Author:</b>@author ziyou<br>
 * <b>Date:</b>2022-10-08 21:31<br>
 * <b>Desc:</b>無<br>
 */
public interface Compresser {
  byte[] compress(byte[] bytes);
  byte[] decompress(byte[] bytes);
}

第二步

再寫兩個對應的實現類,分別是 GzipCompresser.javaWinRarCompresser.java 代碼如下

package com.example.demo.spi.impl;

import com.example.demo.spi.Compresser;

import java.nio.charset.StandardCharsets;

/**
 * <br>
 * <b>Function:</b><br>
 * <b>Author:</b>@author ziyou<br>
 * <b>Date:</b>2022-10-08 21:33<br>
 * <b>Desc:</b>無<br>
 */
public class GzipCompresser implements Compresser {
  @Override
  public byte[] compress(byte[] bytes) {
    return"compress by Gzip".getBytes(StandardCharsets.UTF_8);
  }
  @Override
  public byte[] decompress(byte[] bytes) {
    return "decompress by Gzip".getBytes(StandardCharsets.UTF_8);
  }
}
package com.example.demo.spi.impl;

import com.example.demo.spi.Compresser;

import java.nio.charset.StandardCharsets;

/**
 * <br>
 * <b>Function:</b><br>
 * <b>Author:</b>@author ziyou<br>
 * <b>Date:</b>2022-10-08 21:33<br>
 * <b>Desc:</b>無<br>
 */
public class WinRarCompresser implements Compresser {
  @Override
  public byte[] compress(byte[] bytes) {
    return "compress by WinRar".getBytes(StandardCharsets.UTF_8);
  }

  @Override
  public byte[] decompress(byte[] bytes) {
    return "decompress by WinRar".getBytes(StandardCharsets.UTF_8);
  }
}

第三步

創建配置文件,我們接著在 resources 目錄下創建一個名為 META-INF/services 的文件夾,在其中創建一個名為 com.example.demo.spi.Compresser 的文件,其中的內容如下:

com.example.demo.spi.impl.WinRarCompresser
com.example.demo.spi.impl.GzipCompresser

註意該文件的名稱必須是介面的全路徑,文件裡面的內容每一行都是一個實現類的全路徑,多個實現類就寫在多行裡面,效果如下。

第四步

有了上面的介面,實現類和配置文件,接下來我們就可以使用 ServiceLoader 動態載入實現類,來實現 SPI 技術了,如下所示:

package com.example.demo;

import com.example.demo.spi.Compresser;

import java.nio.charset.StandardCharsets;
import java.util.ServiceLoader;

public class TestSPI {
  public static void main(String[] args) {
    ServiceLoader<Compresser> compressers = ServiceLoader.load(Compresser.class);
    for (Compresser compresser : compressers) {
      System.out.println(compresser.getClass());
    }
  }
}

運行的結果如下

可以看到我們正常的獲取到了介面的實現類,並且可以直接使用實現類的解壓縮方法。

原理

知道瞭如何使用 SPI 接下來我們來研究一下是如何實現的,通過上面的測試我們可以看到,核心的邏輯是 ServiceLoader.load() 方法,這個方法有點類似於 Spring 中的根據介面獲取所有實現類一樣。

點開 ServiceLoader 我們可以看到有一個常量 PREFIX,如下所示,這也是為什麼我們必須在這個路徑下麵創建配置文件,因為 JDK 代碼裡面會從這個路徑裡面去讀取我們的文件。

同時又因為在讀取文件的時候使用了 class 的路徑名稱,因為我們使用 load 方法的時候只會傳遞一個 class,所以我們的文件名也必須是介面的全路徑。

通過 load 方法我們可以看到底層構造了一個 java.util.ServiceLoader.LazyIterator 迭代器。

在迭代器中的 parse 方法中,就獲取了配置文件中的實現類名稱集合,然後在通過反射創建出具體的實現類對象存放到 LinkedHashMap<String,S> providers = new LinkedHashMap<>(); 中。

常用的框架

SPI 技術的使用非常廣泛,比如在 Dubble,不過 Dubble 中的 SPI 有經過改造的,還有我們很常見的資料庫的驅動中也使用了 SPI,感興趣的小伙伴可以去翻翻看,還有 SLF4J 用來載入不同提供商的日誌實現類以及 Spring 框架等。

優缺點

前面介紹了 SPI 的原理和使用,那 SPI 有什麼優缺點呢?

優點

優點當然是解耦,服務方只要定義好介面規範就好了,具體的實現可以由不同的 Jar 進行實現,只要按照規範實現功能就可以被直接拿來使用,在某些場合會被進行熱插拔使用,實現瞭解耦的功能。

缺點

一個很明顯的缺點那就是做不到按需載入,通過源碼我們看到了是會將所有的實現類都進行創建的,這種做法會降低性能,如果某些實現類實現很耗時了話將影響載入時間。同時實現類的命名也沒有規範,讓使用者不方便引用。

總結

阿粉今天給大家介紹了一個 SPI 的原理和實現,感興趣的小伙伴可以自己去嘗試一下,多動手有利於加深記憶哦,如果覺得我們的文章有幫助,歡迎點贊評論分享轉發,讓更多的人看到。


更多優質內容歡迎關註公眾號【Java 極客技術】,我準備了一份面試資料,回覆【bbbb07】免費領取。希望能在這寒冷的日子里,幫助到大家。

本文來自博客園,作者:zi-you,轉載請註明原文鏈接:https://www.cnblogs.com/zi-you/p/16942914.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、寫在前 知識學了就忘!不用就忘!我太健忘!特此記錄!用於複習打卡!Mysql乾就完事了! 二、來辣! MyISAM表把自增主鍵最大id記錄到文件,重啟也不會丟。InnoDB記錄到記憶體,重啟資料庫和OPTIMIZE操作會丟。 Heap表存在於記憶體,用於臨時高速存儲。BLOB和TEXT不允許,只能= ...
  • 首發微信公眾號:SQL資料庫運維 原文鏈接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247485212&idx=1&sn=450e9e94fa709b5eeff0de371c62072b&chksm=ea37536cdd40da7 ...
  • 第一次用vscode寫筆記去同步Cnblog,不知道寫啥就記點常用的md語法吧 1. 標題怎麼寫? 利用“#” + “ ” 即可實現第幾節標題(其中'/',表轉義) > # title1 > ## title2 title1 title2 2. 換行怎麼表示? 註意換行在md以及很多編程語言裡面都是 ...
  • jQuery01 參考文檔1:jQuery API 中文文檔 | jQuery API 中文線上手冊 | jquery api 下載 | jquery api chm (cuishifeng.cn) 參考文檔2:jQuery 教程 (w3school.com.cn),jQuery 教程 | 菜鳥教程 ...
  • 案例介紹 歡迎來到我的小院,我是霍大俠,恭喜你今天又要進步一點點了!我們來用JavaScript編程實戰案例,做一個隨機密碼生成器。用戶點擊生成,輸入框內就會生成一個由數字、大小寫字母、特殊符號隨機組合而成的密碼。通過實戰我們將學會Math.floor方法、substring方法、clipboard ...
  • Error Error是JavaScript語言中的一個標準的內置對象,專門用於處理JS開發中的運行時錯誤。 當我們的JS代碼在運行過程中發生錯誤的話,就會拋出Error對象,整個程式將會中斷在錯誤發生的代碼處,不再繼續執行,這也是錯誤類型必須重視的原因:它會導致頁面無法顯示或者功能失效。 Erro ...
  • File類和IO流 File類 概述 public class File 文件和目錄路徑名的抽象表示 文件和目錄是可以通過File封裝成對象的 封裝的不是文件,而是一個路徑(可以存在,也可以不存在);要通過具體的操作將這個路徑轉化為具體存在 public class FileDemo { publi ...
  • 1.可以從現有的類派生出新類。這稱為類的繼承。新類稱為次類、子類或派生類。現有的類稱為超類、父類或基類。 2.構造方法用來構造類的實例。不同於屬性和方法,子類不繼承父類的構造方法。它們只能用關鍵字super從子類的構造方法中調用。 3.構造方法可以調用重載的構造方法或它的父類的構造方法。這種調用必須 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...