簡體   English   中英

根據運行時提供的依賴關系動態創建Java類的實現

[英]Create implementation of Java class dynamically based on provided dependencies at runtime

我正在嘗試根據運行時類路徑上可用的類來確定創建類的新實例的最佳方法。

例如,我有一個庫,需要在多個類中解析JSON響應。 該庫具有以下界面:

JsonParser.java

public interface JsonParser {
    <T> T fromJson(String json, Class<T> type);
    <T> String toJson(T object);
}

這個類有多個實現,即GsonJsonParserJacksonJsonParserJackson2JsonParser ,目前,庫的用戶需要根據他們在項目中包含的庫來“選擇”他們的實現。 例如:

JsonParser parser = new GsonJsonParser();
SomeService service = new SomeService(parser);

我想做的是,動態地選擇哪個庫在類路徑上,並創建適當的實例,以便庫的用戶不必考慮它(甚至必須知道它的內部實現另一個類解析JSON)。

我正在考慮類似於以下內容:

try {
    Class.forName("com.google.gson.Gson");
    return new GsonJsonParser();
} catch (ClassNotFoundException e) {
    // Gson isn't on classpath, try next implementation
}

try {
    Class.forName("com.fasterxml.jackson.databind.ObjectMapper");
    return new Jackson2JsonParser();
} catch (ClassNotFoundException e) {
    // Jackson 2 was not found, try next implementation
}

// repeated for all implementations

throw new IllegalStateException("You must include either Gson or Jackson on your classpath to utilize this library");

這是一個合適的解決方案嗎? 它似乎有點像黑客,並使用異常來控制流程。

有一個更好的方法嗎?

基本上你想創建自己的JsonParserFactory 我們可以看到它是如何在Spring Boot框架中實現的

public static JsonParser getJsonParser() {
    if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null)) {
        return new JacksonJsonParser();
    }
    if (ClassUtils.isPresent("com.google.gson.Gson", null)) {
        return new GsonJsonParser();
    }
    if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
        return new YamlJsonParser();
    }

    return new BasicJsonParser();
}

因此,除了使用ClassUtils.isPresent方法之外,您的方法幾乎與此相同。

這聽起來像是服務提供者接口(SPI)模式的完美案例。 查看java.util.ServiceLoader文檔以獲取如何實現它的示例。

如果只有一個實現( GsonJsonParserJacksonJsonParserJackson2JsonParser )在運行時出現並且沒有其他選項,那么您必須使用Class.forName()

雖然你可以更聰明地處理它。 例如,您可以將所有類放入Set<String> ,然后循環它們。 如果其中任何一個拋出異常,你可以繼續,而那個沒有,你可以做你的操作。

是的,它是一個黑客,你的代碼將依賴於庫。 如果你有可能在你的類路徑中包含你的JsonParsers的所有三個實現,並使用邏輯來定義你必須使用哪個實現; 這將是一個更好的方法。

如果無法做到這一點,您可以繼續上面的操作。


此外,您可以使用更好的選項Class.forName(String name, boolean initialize, ClassLoader loader) ,而不是使用普通的Class.forName(String name) ,它不會運行任何靜態初始值設定項(如果您的類中存在)​​。

其中initialize = falseloader = [class].getClass().getClassLoader()

簡單的方法是SLF4J使用的方法:使用com.mypackage.JsonParserImpl類為每個底層JSON庫(GSON,Jackson等)創建一個單獨的包裝庫,該類委托給底層庫。 將適當的包裝器放在底層庫旁邊的類路徑中。 然后你可以得到當前的實現,如:

public JsonParser getJsonParser() {
    // needs try block
    // also, you probably want to cache
    return Class.forName("com.mypackage.JsonParserImpl").newInstance()
}

此方法使用類加載器來定位JSON解析器。 它是最簡單的,不需要第三方依賴項或框架。 相對於Spring,Service Provider或任何其他定位資源的方法,我認為它沒有任何缺點。


正如Daniel Pryden所建議的那樣,交替使用服務提供者API。 為此,您仍然為每個底層JSON庫創建一個單獨的包裝器庫。 每個庫都包含位於“META-INF / services / com.mypackage.JsonParser”位置的文本文件,其內容是該庫中JsonParser實現的完全限定名稱。 然后你的getJsonParser方法看起來像:

public JsonParser getJsonParser() {
    return ServiceLoader.load(JsonParser.class).iterator().next();
}

IMO這種方法比第一種方法更復雜。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM