Внедрение с помощью Guice вглубь иерархии зависимостей
Я хочу провести цепочку элементов обработки и соединить их вместе через Guice. Давайте предположим следующий путь:
interface A
осуществляетсяclass AImpl
нужен некоторый вкладinterface B
осуществляетсяclass BImpl
потребностиA
interface C
осуществляетсяclass CImpl
потребностиB
interface D
осуществляетсяclass DImpl
потребностиC
Зависимость A может быть разрешена только во время выполнения, а не во время настройки. Обычный подход заключается в использовании Assisted Injection в этом случае для создания фабрики, которая принимает отсутствующие экземпляры в качестве параметров, вот так:
public interface AFactory {
public A createA(String input);
}
Но то, что я на самом деле хочу, это что-то вроде этого:
public interface DFactory {
public D createD(String inputForA);
}
Я не хочу проходить вручную AImpl
-специфичные зависимости по всей иерархии. Можно ли добиться этого с Guice? Если нет, то как лучше элегантно обойти эту проблему, сохраняя при этом преимущества инъекций?
2 ответа
Обман: Палка input
в статической переменной или синглтоне ThreadLocal
, Установите его до начала конвейера и очистите после его завершения. Свяжите все остальное через DI.
Причудливый путь: в A
обратитесь к @PipelineInput String inputString
но не связывайте это в своем главном инжекторе. В противном случае, связывайте зависимости как обычно, включая ссылку на @PipelineInput
в других классах, связанных с трубопроводом. Когда вам нужно D
получить его от вашей реализации DFactory
, которому я звоню PipelineRunner
,
public class PipelineRunner {
@Inject Injector injector; // rarely a good idea, but necessary here
public D createD(final String inputForA) {
Module module = new AbstractModule() {
@Override public void configure() {
bindConstant(inputForA).annotatedWith(PipelineInput.class);
}
};
return injector.createChildInjector(new PipelineModule(), module)
.getInstance(D.class);
}
}
Естественно, попытки связывания для A
, B
, C
, а также D
потерпит неудачу за пределами PipelineRunner
из-за отсутствия @PipelineInput String
- ты получишь CreationException
когда вы создаете инжектор с этими неудовлетворенными зависимостями, как вы обнаружили, но эти конвейерные зависимости должны быть легко разделены на модуль, который вы устанавливаете в дочерний инжектор.
Если это кажется слишком хакерским, помните, что PrivateModules также " реализованы с использованием родительских инжекторов", и что весь смысл внедрения зависимости заключается в том, чтобы создать зависимость, подобную inputForA
доступный для всего графа объекта в развязанном виде.
Я вижу три варианта. Они зависят от того, как часто вы меняете input
за A
,
1) Bind input
как константа в вашем модуле. Это работает, только если вы знаете это значение до того, как создадите Injector
и никогда не хочу менять значение. Увидеть bindConstant
2) Используйте частный подмодуль, который связывает A
или значение для input
внутри этого модуля. В основном вы можете иметь два или три экземпляра графа с разными значениями. Смотрите newPrivateBinder.
3) Используйте Scope
ала RequestScope
, SessionScope
,... Таким образом, вы можете часто менять ввод, но вы должны войти / выйти из области в некоторой точке, которая будет определена. См. Пользовательские области для примера.