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

bla*_*ide 21 java

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

例如,我有一个库,需要在多个类中解析JSON响应.该库具有以下界面:

JsonParser.java:

public interface JsonParser {
    <T> T fromJson(String json, Class<T> type);
    <T> String toJson(T object);
}
Run Code Online (Sandbox Code Playgroud)

这个类有多种实现方式,即GsonJsonParser,JacksonJsonParser,Jackson2JsonParser,目前,图书馆的用户需要"挑"及其实施基础上,他们已经包括在他们的项目,该项目库中使用.例如:

JsonParser parser = new GsonJsonParser();
SomeService service = new SomeService(parser);
Run Code Online (Sandbox Code Playgroud)

我想做的是,动态地选择哪个库在类路径上,并创建适当的实例,以便库的用户不必考虑它(甚至必须知道它的内部实现另一个类解析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");
Run Code Online (Sandbox Code Playgroud)

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

有一个更好的方法吗?

And*_*niy 10

基本上你想创建自己的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();
}
Run Code Online (Sandbox Code Playgroud)

因此,除了使用该ClassUtils.isPresent方法之外,您的方法几乎与此相同.

  • 感谢您的回答.它看起来像[正是ClassUtils正在做的](https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/ClassUtils. Java的#L325-L334).所以也许我不是完全偏离基地. (2认同)

Dan*_*den 9

这听起来像是服务提供者接口(SPI)模式的完美案例.查看java.util.ServiceLoader文档以获取如何实现它的示例.


Mon*_*nis 5

如果只实现之一(GsonJsonParser,JacksonJsonParser,Jackson2JsonParser)将出现在运行时,有没有其他选择,那么你不得不使用Class.forName().

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

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

如果无法做到这一点,您可以继续上面的操作.


此外,Class.forName(String name)您可以使用更好的选项,而不是使用plain ,Class.forName(String name, boolean initialize, ClassLoader loader)而不会运行任何静态初始化程序(如果您的类中存在)​​.

哪里initialize = falseloader = [class].getClass().getClassLoader()