Статьи

Необязательные зависимости

Иногда библиотека, которую вы пишете, может иметь дополнительные зависимости. Например, «если http-клиент apache находится на пути к классам, используйте его; в противном случае — откат к HttpURLConnection ».

Почему ты бы так поступил? По разным причинам — при распространении библиотеки вы можете и не захотеть навязывать большой след зависимости. С другой стороны, более продвинутая библиотека может иметь преимущества в производительности, поэтому те, кто в ней нуждается, могут включить ее. Или вы можете разрешить легко подключаемые реализации некоторых функций, например, сериализации json. Ваша библиотека не заботится о том, будет ли это сериализация Jackson, gson или нативной android json — так что вы можете предоставить реализации, использующие все это, и выбрать ту, чья зависимость найдена.

Один из способов добиться этого — явно указать / передать используемую библиотеку. Когда пользователь вашей библиотеки / платформы создает экземпляр своего основного класса, он может передать логическое значение useApacheClient=true или значение перечисления JsonSerializer.JACKSON . Это неплохой вариант, так как он заставляет пользователя знать, какую зависимость он использует (и является фактическим внедрением зависимости)

Другим вариантом, используемым Spring среди других, является динамическая проверка, доступна ли зависимость на пути к классам. Например

01
02
03
04
05
06
07
08
09
10
11
private static final boolean apacheClientPresent = isApacheHttpClientPresent();
private static boolean isApacheHttpClientPresent() {
  try {
    Class.forName("org.apache.http.client.HttpClient");
    logger.info("Apache HTTP detected, using it for HTTP communication.);
    return true;
  } catch (ClassNotFoundException ex) {
    logger.info("Apache HTTP client not found, using HttpURLConnection.");
    return false;
  }
}

и затем всякий раз, когда вам нужно сделать HTTP-запросы (где ApacheHttpClient и HttpURLConnectionClient являются вашими собственными реализациями вашего собственного интерфейса HttpClient):

1
2
3
4
5
6
HttpClient client = null;
if (apacheClientPresent) {
   client = new ApacheHttpClient();
} else {
   client = new HttpURLConnectionClient();
}

Обратите внимание, что важно защищать любой код, который может пытаться загрузить классы из зависимости, с помощью логического значения «isXPresent». В противном случае исключения загрузки классов могут вылететь. Например, весной они обернули зависимости Джексона в MappingJackson2HttpMessageConverter

1
2
3
if (jackson2Present) {
    this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}

Таким образом, если Джексона нет, класс не создается, а загрузка классов Джексона вообще не предпринимается.

Предпочитать ли автоматическое обнаружение или требовать явной настройки используемой зависимости — это сложный вопрос. Поскольку автоматическое обнаружение может оставить пользователя вашей библиотеки неосведомленным о механизме, и когда они добавляют зависимость для другой цели, она может быть выбрана вашей библиотекой, и поведение может измениться (хотя это не должно происходить, крошечные различия всегда присутствуют) , Вы должны документировать это, конечно, и даже регистрировать сообщения (как указано выше), но этого может быть недостаточно, чтобы избежать (не) приятных сюрпризов. Поэтому я не могу ответить, когда использовать какой, и это должно быть решено в каждом конкретном случае.

Этот подход применим также к внутренним зависимостям — ваш базовый модуль может искать более конкретный модуль, который будет присутствовать, чтобы использовать его, и в противном случае откатится до значения по умолчанию. Например, вы предоставляете реализацию «истекшего времени» по умолчанию с помощью System.nano() , но при использовании Android для этого лучше использовать SystemClock — поэтому вы можете определить, присутствует ли ваша реализация Android за истекшее время. Это выглядит как логическая связь, поэтому в этом сценарии, возможно, разумнее предпочесть явный подход.

В целом, это хороший метод для использования необязательных зависимостей с базовым отступлением; или один из многих возможных вариантов без отступления. И хорошо знать, что вы можете сделать это, и иметь это в своем «наборе инструментов» возможных решений проблемы. Но вы не всегда должны использовать его поверх явного (внедрение зависимости).