Подтвердить что ты не робот

Создавать реализацию класса Java динамически на основе предоставленных зависимостей во время выполнения

Я пытаюсь определить лучший способ создать новый экземпляр класса, основанный на том, какие классы доступны в пути к классам во время выполнения.

Например, у меня есть библиотека, которая требует, чтобы ответ JSON обрабатывался несколькими классами. Библиотека имеет следующий интерфейс:

JsonParser.java:

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

Этот класс имеет несколько реализаций, то есть GsonJsonParser, JacksonJsonParser, Jackson2JsonParser, и в настоящее время пользователь библиотеки должен "выбрать" свою реализацию, которая будет использоваться на основе той библиотеки, в которую они были включены их проект. Например:

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");

Будет ли это подходящим решением? Это похоже на хак, а также использует исключения для управления потоком.

Есть ли лучший способ сделать это?

4b9b3361

Ответ 1

По существу, вы хотите создать свой собственный JsonParserFactory. Мы видим, как он реализован в Spring Структура загрузки:

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.

Ответ 3

Если только одна из реализаций (GsonJsonParser, JacksonJsonParser, Jackson2JsonParser) будет присутствовать во время выполнения, и другой опции нет, тогда вам нужно будет использовать Class.forName().

Хотя вы можете справиться с этим умнее. Например, вы можете поместить все классы в Set<String>, а затем перебрать их. Если какой-либо из них вызывает исключение, вы можете просто продолжить, а тот, который этого не делает, вы можете выполнять свои операции.

Да, это взломать, и ваш код станет зависимым от библиотеки. Если есть вероятность, что вы можете включить все три реализации JsonParsers в ваш путь к классам и использовать логику, чтобы определить, какую реализацию вы должны использовать; это будет гораздо лучший подход.

Если это невозможно, вы можете продолжить работу выше.


Кроме того, вместо простого Class.forName(String name) вы можете использовать лучший вариант Class.forName(String name, boolean initialize, ClassLoader loader), который НЕ запускает никаких статических инициализаторов (если они присутствуют в вашем классе).

Где initialize = false и loader = [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, поставщика услуг или любого другого метода поиска ресурсов.


Альтернативно используйте API-интерфейс поставщика услуг, как предлагает Даниэль Приден. Для этого вы по-прежнему создаете отдельную библиотеку-оболочку для каждой базовой библиотеки JSON. Каждая библиотека включает текстовый файл в месте "META-INF/services/com.mypackage.JsonParser", чье содержимое является полнофункциональным именем реализации JsonParser в этой библиотеке. Тогда ваш метод getJsonParser будет выглядеть так:

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

ИМО этот подход излишне сложнее первого.