【问题标题】:Create implementation of Java class dynamically based on provided dependencies at runtime在运行时根据提供的依赖项动态创建 Java 类的实现
【发布时间】:2016-05-23 00:38:33
【问题描述】:

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

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

这是一个合适的解决方案吗?它看起来有点像 hack,并且使用异常来控制流程。

有没有更好的方法来做到这一点?

【问题讨论】:

  • 我同意,这是一个 hack。选择一个 JSON 解析器依赖项并使用它?您的方法也可能意外地不确定;根据 JAR(可能还有类路径中 JAR 的顺序),程序行为可能会发生变化。
  • @ElliottFrisch 我想这样做,但目前将使用它的应用程序具有这些解析器中的任何一个,我试图避免让第二个成为依赖项,如果它是不同于我决定打包的那个(或相同的库,不同的版本)。
  • @Andremoniy 是的,Spring 也这样做;它仍然是一个hack。我什至承认这似乎是一个聪明的黑客攻击。但是,如果 Java 9+ 添加了一个内置的 JSON 解析器,那么它追溯就变成了?
  • @Casey 当类路径上的类丢失时,如何编译实现?

标签: java


【解决方案1】:

基本上你想创建自己的JsonParserFactory。我们可以在Spring Boot framework看到它是如何实现的:

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 method

【讨论】:

  • 感谢您的回答。它看起来像that's exactly what ClassUtils is doing。所以也许我没有完全在基地之外。
  • @Casey 你是绝对正确的,我错过了ClassUtils 本身是 Spring 的一部分,所以我编辑了我的答案。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    如果在运行时只有一个实现(GsonJsonParserJacksonJsonParserJackson2JsonParser)并且没有其他选项,那么您必须使用Class.forName()

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

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

    如果这不可行,您可以继续上述操作。


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

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

    【讨论】:

      【解决方案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 的建议,交替使用 Service Provider API。为此,您仍然为每个底层 JSON 库创建一个单独的包装器库。每个库都包含一个位于“META-INF/services/com.mypackage.JsonParser”位置的文本文件,其内容是该库中JsonParser 实现的完全限定名称。那么您的 getJsonParser 方法将如下所示:

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

      IMO 这种方法比第一种方法复杂得多。

      【讨论】:

      • 有趣的解决方案,听起来绝对比我提出的方案更好,更易于维护。我测试了 Service Provider API,它看起来并不复杂,只是在每个包装器的 META-INF 中添加该文件有点烦人。我确实喜欢它允许类保持不同的名称,即GsonJsonParserJacksonJsonParser 而不是JsonParserImpl,但我想这纯粹是语义......
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多