字節面試也會問SPI機制?

語言: CN / TW / HK

​1.前言

Java SPI 機制,主要是類加載器反雙親委派的實現(第三方包不在指定jdk路徑,一般類加載器無法加載,需要特殊的ContextClassLoader加載以便使用)。本次將對 SPI機制進行詳解,並結合案例介紹其在實際場景中具體使用。

提示:以下是本篇文章正文內容,案例僅供對比參考

2.什麼是SPI機制?

  • SPI(全稱:Service Provider Interface),是jdk內置的一種服務提供發現接口機制,旨在由第三方服務實現或擴展為組件,方便開發人員快速集成指定擴展組件滿足指定的需求。這對於應用或平台擴展來説,無疑是一種成本較低、動態靈活的方案。
  • SPI機制調度過程(業務調用方可根據加載的擴展實現類實現功能)
  • 調用流程

3.實現方式及使用場景

鑑於目前實際項目涉及範圍,總結出的常見應用場景。

3.1 接口權限定文件名方式

即在resource文件下創建META/services/目錄,並在此目錄下新建文件,文件名稱為接口類權限定文件名,如 com.lgy.spidemo.serviceway.SpiService。(不好理解就是接口類的package地址 + 接口類名)

使用場景一:

  • 場景描述:不同部門類型的員工需要從不同的考勤應用獲取出勤信息,如職能部門僅拉取釘釘考勤,業務部門需要拉取釘釘考勤的基礎上再結合自研考勤模塊數據彙總出勤結果。
  • 實現方式:抽象通用拉取考勤接口,定義不同部門人員考勤統計實現類。
  • 直接上代碼:
  • 通用接口:
package com.lgy.spidemo.serviceway;

 /**
  * @description: 考勤接口
  **/
 public interface AttendanceService {
  void pullAttendanceInfos();
 }
  • 職能部門考勤實現類;
/**
  * @description: 職能部門考勤實現
  **/
 public class FunctionAttendanceServiceImpl implements AttendanceService  {
  @Override
  public void pullAttendanceInfos() {
   System.out.println(" FunctionAttendanceService implements ...");
   // 邏輯忽略
  }
 }
  • 銷售部門考勤實現;
/**
  * @description: 銷售部門考勤實現
  **/
 public class SaleAttendanceServiceImpl implements AttendanceService  {
  @Override
  public void pullAttendanceInfos() {
   System.out.println(" SaleAttendanceService implements ...");
   // 邏輯忽略
  }
 }
  • 測試類;
/**
  * 1、項目的\src\main\resources\下創建\META-INF\services目錄
  * 2、META-INF\services的目錄下再增加一個配置文件,這個文件必須以接口的全限定類名保持一致 (com.lgy.spidemo.service.SpiService)
  * 3、在配置文件中寫入具體實現類的全限定類名,如有多個便換行寫入 com.lgy.spidemo.service.impl.SaleAttendanceServiceImpl
 com.lgy.spidemo.service.impl.FunctionAttendanceServiceImpl
  **/
 public class AttendanceServiceTest {
  public static void main(String[] args) {
   ServiceLoader<AttendanceService> services =
      ServiceLoader.load(AttendanceService.class);
   // 省略判斷人員部門類型邏輯
   // 測試輸出結果,展示實現接口已加載
   for (AttendanceService service : services) {
    service.pullAttendanceInfos();
   }
  }
 }
  • 測試結果如下;
// 兩個實現類均被加載成功,在實際使用時,可根據需要去調用不同的實現。
     FunctionAttendanceService implements ...
     SaleAttendanceService implements ....

實現類不要標註任何註解,不然Spring在初始化過程中掃描並加載,無法測試。

結合場景一分析:

  • 此場景可以通過自定義實現類的方式滿足業務需求(不同部門的考勤規則),有助於業務實現快速迭代,同時也提升了服務架構的可拓展性。
  • 考慮公司組織架構比較複雜,部門職責分的比較細,後續擴展機率比較大,比如職能部門行政類和運營類標準細分,很可能會增加除了考勤之外的各種考核指標等,借鑑此方案可能簡單實現並比較方便集成,使得業務間減少依賴,實現解耦的設計模式,因此個人是比較偏向用此方案。
  • 其它應用:如項目中常用的日誌也是採用SPI機制,常見的common-logging的LogFatory就是標準SPI接口,有興趣的可以自行研究。

3.2 spring.factories方式

  • 和上面一樣,需要在resource文件下創建META/services/目錄,並在此目錄下新建文件,區別在於文件名為spring.factories。

使用場景二

  • 場景描述:針對於不同的開發端使用習慣展示不同的接口文檔,比如APP端習慣於Swagger,JAVA端喜歡dateway風格,就在不同實例展示不同接口文檔。此場景是我臆想出來。
  • 實現方式:構建兩種版本的jar包,比如 1.0.0-swagger 、2.0.0-dataway,再對應的包內配置spring.factories內的config配置類。

代碼如下:

package com.lgy.spidemo.factoriesway;

 import org.springframework.boot.autoconfigure.AutoConfigurationImportEvent;
 import org.springframework.boot.autoconfigure.AutoConfigurationImportListener;

 /**
  * @description: 自動配置swagger
  **/
 public class SwaggetAutoConfiguration {
  public SwaggetAutoConfiguration() {
   System.out.println(" SwaggetAutoConfiguration init ...");
  }
  // 配置內容省略
 }

 /**
  * @description: 自動配置dataway
  **/
 public class DataWayAutoConfiguration {
  public DataWayAutoConfiguration() {
   System.out.println(" DataWayAutoConfiguration init ...");
  }
  // 配置內容省略
 }

 /**
 * resource/META-INFO/spring.factories 文件內容 *
 * org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
   com.lgy.spidemo.factoriesway.SwaggetAutoConfiguration
 * 輸出結果:SwaggetAutoConfiguration init ...
 **/
  • 根據spring.factories內配置的類,在springboot啟動初始化過程中會自動加載對應的配置,實現所需的接口文檔。

結合場景二分析:

  • spring.factories實現機制與上述方式一致,只是實現方式不同,本質目的是通過抽象化類的方式,實現解耦,最終便於擴展
  • 其它使用場景:如spring-boot-autoconfigure-x.x.x.RELEASE.jar,就是通過此方式完成初始化加載。

4.總結

本次講解的兩種方式均是基於SPI機制,可見是多麼受開發追捧。當然,還有很多種實現方式,我個人覺得最主要的還是能夠在自己的掌控範圍內去使用,畢竟有問題可以通過自己的學習理解去解決。

最後説一句,沒有更好的技術知識,只有更適合的技術應用,結合實際,檢出真理。