將ServiceLoader遷移到Java 9模組系統 - frankel

banq發表於2019-10-15

Service Loader允許在不同的JAR中分離API及其實現。客戶端程式碼僅取決於API,而在執行時,將使用類路徑上的實現。這是將客戶端程式碼與實現程式碼分離的好方法。
為了說明這一點,讓我們實現自己的日誌記錄專案:

public interface LogService {
    void log(String message);
}

public class LogStdOut implements LogService {
    @Override
    public void log(String message) {
        System.out.println(message);
    }
}


呼叫API並利用服務載入程式機制的客戶端:

public class Client {

    public static void main(String[] args) {
        ServiceLoader<LogService> loader = ServiceLoader.load(LogService.class);
        for (LogService service : loader) {
            service.log("Log written by " + service.getClass());
        }
    }
}

之所以如此神奇,是因為客戶端包含一個滿足一些約束的服務載入器配置檔案:
  1. 它位於 /META-INF/services
  2. 它的名稱是介面的標準名稱
  3. 它包含實現類的標準類名:/META-INF/services/ch.frankel.blog.serviceloader.log.LogServicech.frankel.blog.serviceloader.log.stdout.LogStdOut


遷移到Java平臺模組系統

關於我們的示例專案,需要執行以下步驟。
1.模組化API: 為了使其他模組(實現和客戶端)使用API​​,LogService需要匯出包含介面的包。
module-info.java

module log.api {
    exports ch.frankel.blog.serviceloader.log;
}


2.模組化客戶端: 客戶端位於模組依賴關係樹的邊界。它只需要API模組。
module-info.java

module log.client {
    requires log.api;
}


3. 模組化實施: 實現需要API。它還應該匯出包含實現的包,以便可以在其他模組中使用。但是,這還不夠。Java 9取代了Service Loader的工作方式,從META-INF/services資料夾到特定於模組的實現。
為此,module-info語法提供了兩個關鍵字:使用provides引用介面並使用with指定實現:
module-info.java

import ch.frankel.blog.serviceloader.log.LogService;
import ch.frankel.blog.serviceloader.log.stdout.LogStdOut;

module log.stdout {
    requires log.api;
    exports ch.frankel.blog.serviceloader.log.stdout;
    provides LogService
        with LogStdOut;
}


有趣的是,僅配置發生了變化:客戶端中的Service Loader程式碼本身沒有變化。
 

相關文章