Как использовать два модуля Guice, которые устанавливают общий модуль зависимостей

Я работаю над проектом, состоящим из четырех частей:

  • Проект Main, который объединяет все. Это содержит точку входа public static void main(String... args).
  • Компонент A
  • Компонент B
  • Компонент Common стороннего участника, на который ссылаются как A, так и B.

Я использую Guice для сантехники между четырьмя частями, и это моя проблема:
В A и B основных модулях Guice я устанавливаю модуль, который расширяет тот, который определен в Common. Во время выполнения эта установка не работает со следующей ошибкой:

Связывание с common.SomeClass уже было настроено на common.AbstractCommonModule.configure(). [источник]

Причиной этого является то, что я вызываю common.AbstractCommonModule.configure() дважды; один раз, установив экземпляр подкласса common.AbstractCommonPrivateModule из Component A com.a.MainModule.configure() и второй раз из Component B com.b.MainModule.configure().

Установка только одного экземпляра common.AbstractCommonPrivateModule в Main не является опцией, потому что AbstractCommonPrivateModule реализует специальный метод связывания bindComplicatedStuff(ComplicatedStuff), для которого я знаю только аргумент внутри A и B, соответственно.

Я попытался обойти все это, обернув A и B соответствующие основные модули Guice в PrivateModule s. Однако это не удалось с следующей ошибкой:

Невозможно создать привязку для% s. Он уже был настроен для одного или нескольких дочерних инжекторов или частных модулей% s% n Если бы это было в PrivateModule, вы забыли открыть привязку? [источник]

В моем случае A и B соответствующие основные модули Guice фактически ServletModule - которые, по-видимому, я могу установить дважды от Main.

Как обойти эти ошибки и установить модуль AbstractCommonPrivateModule дважды?

Изменить: Я загрузил некоторый пример кода (с объяснением некоторых деталей) в GitHub

Ответы

Ответ 1

Вместо A и B установите Common, имейте их requireBinding() для классов, которые им нужны из Common. Затем модули, которые полагаются на A или B, также должны установить Common. Это может показаться немного странным, но это действительно желательно, поскольку A и B теперь менее тесно связаны с Common.


Обновление

Причина, по которой я устанавливаю два ShiroWebModule, заключается в том, что я хочу, чтобы ресурсы Джерси в модуле ui были защищены только с помощью одной конфигурации Shiro (которая не требует защиты паролем ресурсов), а все ресурсы Джерси в api должен быть защищен с использованием совершенно другой конфигурации Shiro (той, которая понимает только токены-носители в качестве механизма аутентификации).

В широком смысле это невозможно. Guice Injector предоставляет один способ сделать что-то (как правило, одну реализацию интерфейса) для всего приложения; не разные механизмы для каждой упаковки. Ваши два Module s, SwsApiServletModule и SwsUiServletModule предоставляют несколько идентичных привязок, а SwsModule устанавливает их оба вместе. В сущности, вы говорите "Guice, пожалуйста, предоставьте механизм аутентификации на основе токена", а затем сразу после "Guice, пожалуйста, предоставьте механизм аутентификации на основе пароля". Он может делать только один или другой, поэтому вместо того, чтобы выбирать один произвольно, он не работает быстро.

Конечно, существует ряд решений, в зависимости от ваших потребностей. Наиболее распространенным является использование привязки аннотаций и для запроса пользовательского интерфейса и кода API различной аннотации. Таким образом, вы можете установить две разные реализации (с разными аннотациями) одного и того же интерфейса или класса.

Вот пример:

package api;

public class ApiResources {
  @Inject
  public ApiResources(@ApiAuthMechanism AuthMechanism auth) {
    this.auth = auth;
  }
}

---

package api;

public class ApiModule implements Module {
  public void configure() {
    bind(AuthMechanism.class).annotatedWith(ApiAuthMechanism.class)
        .to(BearerTokenAuthMechanism.class);
  }
}

---

package ui;

public class UiResources {
  @Inject
  public UiResources(@UiAuthMechanism AuthMechanism auth) {
    this.auth = auth;
  }
}

---

package ui;

public class UiModule implements Module {
  public void configure() {
    bind(AuthMechanism.class).annotatedWith(UiAuthMechanism.class)
        .to(PasswordAuthMechanism.class);
  }
}

---

package webap;

public class WebappModule implements Module {
  public void configure() {
    // These modules can be installed together,
    // because they don't install overlapping bindings 
    install(new ApiModule());
    install(new UiModule());
  }
}

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

Другим вариантом, который не идеален, но может работать в некоторых случаях использования, является использование двух полностью изолированных экземпляров Injector, по одному с каждой привязкой. Затем вы вручную передаете экземпляры, необходимые для кода пользовательского интерфейса и API. Это несколько поражает цель Гиса, но это не всегда неправильное решение. Использование child Injector s может сделать это менее болезненным.


В стороне, ваш "образец кода" огромен, и, вероятно, более 90% не связано с проблемой. В будущем, пожалуйста, найдите время, чтобы создать SSCCE, который содержит только код, имеющий отношение к проблеме. Просто никто не собирается просеивать более 100 Java файлов и 7,300+ строк кода, чтобы понять вашу проблему. Это не только облегчит людям, которые пытаются вам помочь, но просто попытка создать SSCCE, который демонстрирует проблему, часто будет достаточно, чтобы помочь вам понять и решить ее самостоятельно.