首頁 > 軟體

JAVA SPI機制詳解使用方法

2022-07-05 14:05:22

寫在前面

Java SPI提供了一種為某個介面尋找服務實現的機制。有點類似IOC的思想,就是將裝配的控制權移到程式之外,在模組化設計中這個機制尤其重要,SPI的核心思想就是解耦。

什麼是SPI

SPI全稱Service Provider Interface,是Java提供的一套用來被第三方實現或者擴充套件的API,它可以用來啟用框架擴充套件和替換元件。Java SPI 實際上是“基於介面的程式設計+策略模式+組態檔”組合實現的動態載入機制。在物件導向的程式設計與設計中,一般推薦模組之間要基於介面程式設計,模組之間不對實現類進行寫死,一旦程式碼裡涉及了具體的實現類,就違反了可拔插的原則,如果需要替換另外一種實現,就需要修改程式碼。

使用場景

使用Java SPI機制的優勢是實現解耦,使得第三方服務模組的裝配控制的邏輯與呼叫者的業務程式碼分離,而不是耦合在一起,應用程式可以根據實際業務情況啟用框架擴充套件或替換框架元件,常見的場景如下:
(1). JDBC載入不同型別的驅動
(2). SLF4J對Log4j/Logback的支援

實現約定

(1). 服務提供者提供介面的具體實現,在JAVA包的META-INF/services目錄下建立一個以“介面全限定名”為命名的檔案,內容為實現類的全限定名;
(2). 介面具體實現類所在的JAR包需要放在主程式的CLASSPATH中;
(3). 主程式通過java.util.ServiceLoder動態載入具體的實現模組,它通過掃描META-INF/services目錄下的組態檔,找到具體實現類並把它載入到JVM中;
(4). SPI的實現類必須攜帶一個不帶引數的建構函式。

四種角色

(1). 提供某種功能的介面(SLF4J 提供了一組介面類)
(2). 提供某種功能介面的具體實現(每個具體的實現需要包含:META-INF/services目錄下建立一個以“介面全限定名”為命名的檔案,內容為實現類的全限定名。Log4j/Logback提供了具體的實現)
(3). 提供發現和載入CLASSPATH中所有的介面具體實現的物件
(4). 使用者端(介面的使用者)

關於JAVA SPI詳細的介紹請看:JAVA - SPI機制使用詳解(一)

基於JAVA原生特性實現的JAVA SPI機制的DEMO

1. 主要角色

主要角色有:介面、多個實現類以及測試使用者端,在每個實現類中需要建立資訊檔案:resources/META-INF/services/介面全限定名一致的檔案。介面、多個實現類以及使用者端分別在不同的MODULE中。

2. 範例程式碼

①. 介面:

package com.hadoopx.javax.spi;
public interface Coder {
    public String write();
}

②. 第一個實現類:

package com.hadoopx.javax.spi;
public class Javaer implements Coder {
    public String write() {
        return "I'M JAVA CODER, USE JAVA TO WRITE EVERYTHING.";
    }
}
建立資訊說明檔案:resources/META-INF/services/com.hadoopx.javax.spi.Coder,
裡面的內容為:com.hadoopx.javax.spi.Javaer

③. 第二個實現類:

package com.hadoopx.javax.spi;
public class Rubyer implements Coder {
    public String write() {
        return "I'M RUBY CODER, USE RUBY TO WRITE EVERYTHING.";
    }
}
建立資訊說明檔案:resources/META-INF/services/com.hadoopx.javax.spi.Coder,
裡面的內容為:com.hadoopx.javax.spi.Rubyer

④. 使用者端:

新增依賴:
<dependencies>
    <dependency>
        <groupId>com.hadoopx</groupId>
        <artifactId>javax-spi001-javaer</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.hadoopx</groupId>
        <artifactId>javax-spi001-rubyer</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>
使用者端程式碼:
package com.hadoopx.javax.spi;
public class MyTest {
    public static void main(String[] args) {
        ServiceLoader<Coder> s = ServiceLoader.load(Coder.class);
        Iterator<Coder> iterator = s.iterator();
        while (iterator.hasNext()) {
            Coder lang =  iterator.next();
            String content = lang.write();
            System.out.println(content);
        }
    }
}

輸出結果為:

I'M JAVA CODER, USE JAVA TO WRITE EVERYTHING.
I'M RUBY CODER, USE RUBY TO WRITE EVERYTHING.

3. 說明

在實際的使用過程中,需要指定不同的型別來建立不同的實現類範例。

基於SPRING BOOT實現的JAVA SPI機制的DEMO

注意: 在每個實現類中不需要建立資訊檔案。

①. 介面:

package com.hadoopx.javax.spi;
public interface Coder {
    public String write();
}

②. 第一個實現類:

package com.hadoopx.javax.spi;
@Service
@Primary
public class Javaer implements Coder {
    public String write() {
        return "I'M JAVA CODER, USE JAVA TO WRITE EVERYTHING.";
    }
}

③. 第二個實現類:

package com.hadoopx.javax.spi;
@Service
public class Rubyer implements Coder {
    public String write() {
        return "I'M RUBY CODER, USE RUBY TO WRITE EVERYTHING.";
    }
}

④. 使用者端一:

新增依賴:
<dependencies>
    <dependency>
        <groupId>com.hadoopx</groupId>
        <artifactId>javax-spi002-javaer</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.hadoopx</groupId>
        <artifactId>javax-spi002-rubyer</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>
使用者端程式碼:
@RestController
@RequestMapping("/t")
@Api(value = "測試服務", description = "該服務僅僅用於完成驗證、學習和測試")
public class TestController {
	// 切換不同的服務
    @Autowired
    @Qualifier("javaer")
    private Coder coder;
    @ApiOperation(value = "測試", notes = "基於SPRING BOOT實現的JAVA SPI機制的DEMO")
    @GetMapping("/spi")
    public String test() {
        System.out.println(coder.write());
        return "ok";
    }
}

輸出結果為:

I'M JAVA CODER, USE JAVA TO WRITE EVERYTHING.

⑤. 使用者端二:

有時會根據不同的情況,呼叫不同服務的方法,所以在使用者端中需要多增加下面這個檔案:

@Service
public class CoderContext {
	// 通過 @Autowired 把Coder所有的實現類注入到map(coders)中.
	// Spring會查詢應用的上下文裡型別為Coder的Bean, 並把查詢到的Bean注入到Map<String, Coder> 或者 List<Coder>中
    @Autowired
    Map<String, Coder> coders;
    public Coder getCoder(String key){
        return coders.get(key);
    }
}
使用者端程式碼:
@RestController
@RequestMapping("/t")
@Api(value = "測試服務", description = "該服務僅僅用於完成驗證、學習和測試")
public class TestController {
    @Autowired
    private CoderContext coderContext;
    @ApiOperation(value = "測試", notes = "基於SPRING BOOT實現的JAVA SPI機制的DEMO")
    @GetMapping("/spi")
    public String test(String type) {
        System.out.println(coderContext.getCoder(type).write());
        return "ok";
    }
}

到此這篇關於JAVA SPI機制詳解使用方法的文章就介紹到這了,更多相關JAVA SPI機制內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com