Определение зависимостей во время выполнения - PullRequest
4 голосов
/ 11 марта 2012

Я сомневаюсь, как реализовать внедрение зависимостей с помощью guice, когда существует несколько реализаций одного и того же интерфейса, и эта зависимость определяется во время выполнения на основе параметров, поэтому я приведу пример, чтобы легко объяснить мой вопрос:

Представьте себе сценарий, в котором у вас есть один модуль для загрузки файлов нескольких форматов, в основном у вас есть один интерфейс, определяющий контракт, и несколько реализаций по одному для каждого формата:

public interface FileLoader {
    void load(File file);
}

public class YMLFileLoader{
    void load(File file){
    System.out.println("Loading YML");
    }
}

public class XMLFileLoader{
     void load(File file){
         System.out.println("Loading XML");
     }
}

Теперь во время выполнения необходимо определить на основе расширения файла реализацию, которая должна использоваться для его загрузки. Моя идея поддерживать код в чистоте - использовать аннотации, для каждой реализации указывается, что она загружает через аннотацию @FileLoaderType.

@Singleton
@FileLoaderType("yml")
public class YMLFileLoader{
    void load(File file)
    {
        System.out.println("Loading YML");
    }
}

@Singleton
@FileLoaderType("xml")
public class XMLFileLoader{
    void load(File file)
    {
        System.out.println("Loading XML");
    }
}

Мой первый вопрос: возможна ли реализация?

Поскольку первый вопрос положителен, есть ли способ реализовать это решение, если для каждой новой реализации FileLoader не требуется рефакторинг в реализации AbstractModule, поддерживающей это решение? Другими словами, в основном для каждой новой реализации FileLoader требуется только наличие аннотации @FileLoaderType, чтобы Guice знал, какую зависимость он должен внедрить, если расширение соответствует ей.

1 Ответ

3 голосов
/ 15 марта 2012

Одна вещь, которую Guice не может сделать, это то, что он не может просканировать ваш путь к классам и найти, какие у вас есть классы, поэтому вам понадобится какой-то способ сообщить guice, какие классы у вас есть. Поэтому давайте разделим ваш вопрос на две половины: получение списка классов реализации FileLoader и привязка этих классов к Guice.

Позвольте мне сначала заняться второй половиной. Я предполагаю, что в вашем подклассе AbstractModule есть метод с именем getFileLoaderClasses с подписью:

private List<Class<? extends FileLoader>> getFileLoaderClasses() { ... }

В этом случае я бы порекомендовал для привязки реализаций FileLoader что-то вроде этого:

private void bindFileLoaders() {
  MapBinder<String, FileLoader> mapBinder
      = MapBinder.newMapBinder(binder(), String.class, FileLoader.class);
  for (Class<? extends FileLoader> implClass : getFileLoaderClasses()) {
    FileLoaderType annotation = implClass.getAnnotation(FileLoaderType.class);
    if (annotation == null) {
      addError("Missing FileLoaderType annotation on " + implClass.getClass());
      continue;
    }
    mapBinder.addBinding(annotation.getValue()).to(implClass);
  }
}

Для этого требуется расширение guice-multibindings . После того, как вы привяжете подобные реализации, вы можете использовать его как:

public class MyLoader {
  @Inject Map<String, FileLoader> fileLoaderMap;

  public void load(File file, String type) {
    FileLoader fileLoader = fileLoaderMap.get(type);
    if (fileLoader == null) {
      throw new IllegalArgumentException("No file loader for files of type " + type);
    }
    fileLoader.load(file);
  }
}

Итак, как нам получить этот полный список классов реализации? Есть несколько способов сделать это:

  • Один класс со статическим списком всех классов реализации:

    public class FileLoaderRegistry {
      public static final List<Class<? extends FileLoader>> impls =
          ImmutableList.of(
            YMLFileLoader.class,
            XMLFileLoader.class,
            JsonFileLoader.class
          );
    }
    

    Это имеет то преимущество, что это, вероятно, самое простое решение для реализации. Недостатком является то, что вам нужно будет обновлять этот файл при каждой новой реализации и перекомпилировать его.

  • Имейте текстовый файл со всеми именами классов в нем и загружайте классы с Class.forName() после прочтения всех строк файла.

  • Если у вас есть реализации FileLoader в разных файлах jar, вы можете иметь текстовый файл со списком имен классов в каждом jar в общем месте, а затем использовать System.getResources(), чтобы получить Enumeration URL с указанием на каждый из текстовых файлов. Затем прочитайте каждый файл и загрузите объекты класса с помощью Class.forName().

  • Самым сложным вариантом является использование инструмента обработки аннотаций в качестве части процесса компиляции и использования его для создания текстовых файлов или класса реестра. Это отдельный вопрос.

...