根据运行时提供的依赖关系动态创建Java类的实现

时间:2016-05-15 15:05:48

标签: java

我正在尝试根据运行时类路径上可用的类来确定创建类的新实例的最佳方法。

例如,我有一个库,需要在多个类中解析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");

这是一个合适的解决方案吗?它似乎有点像黑客,并使用异常来控制流量。

有更好的方法吗?

4 个答案:

答案 0 :(得分:10)

基本上你想创建自己的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(); } 。我们可以在Spring Boot framework

中看到它是如何实现的
void Parent::changeText(int button, QString text)
{
    PB[button].setText(text);
}

所以除了使用ClassUtils.isPresent method之外,你的方法与此几乎相同。

答案 1 :(得分:9)

这听起来像是Service Provider Interface (SPI)模式的完美案例。查看java.util.ServiceLoader文档,了解如何实现它。

答案 2 :(得分:5)

如果只有一个实现(GsonJsonParserJacksonJsonParserJackson2JsonParser)在运行时出现且没有其他选项,那么你必须使用{{1 }}

虽然你可以更聪明地处理它。 例如,您可以将所有类放入Class.forName(),然后循环它们。如果其中任何一个抛出异常,你可以继续,而那个没有,你可以做你的操作。

是的,它是一个黑客,你的代码将依赖于库。如果你有可能在你的类路径中包含你的JsonParsers的所有三个实现,并使用逻辑来定义你必须使用哪个实现;这将是一个更好的方法。

如果无法做到这一点,您可以继续上面的步骤。

此外,您可以使用更好的选项Set<String>,而不是使用普通Class.forName(String name),它不会运行任何静态初始化程序(如果您的类中存在)​​。

Class.forName(String name, boolean initialize, ClassLoader loader)initialize = false

的位置

答案 3 :(得分:4)

简单方法是SLF4J使用的方法:为每个底层JSON库(GSON,Jackson等)创建一个单独的包装器库,其中com.mypackage.JsonParserImpl类委托给底层库。将适当的包装器放在底层库旁边的类路径中。然后你可以获得当前的实现,如:

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这种方法比第一种方法更复杂。