Вы неправильно думаете об инъекции зависимостей. Внедрение зависимостей и локатор сервисов являются зеркальным отображением друг друга: с помощью локатора сервисов вы запрашиваете у него объект. При внедрении зависимостей вы не ищете зависимости, они просто передаются вам.
По сути, «это черепахи до самого низа»! Каждая зависимость вашего класса должна быть внедрена. Если MyApplication
нужен объект MyConfiguration
, он должен просто принять объект MyConfiguration
в качестве параметра конструктора и не беспокоиться о том, как он был сконструирован.
Это не означает, что вы никогда не сможете использовать new
вручную, но вы должны зарезервировать это для объектов типа значения, которые не имеют внешних зависимостей. (И в этих случаях я бы сказал, что в любом случае вам лучше использовать статический фабричный метод, чем публичный конструктор, но это не относится к делу.)
Теперь есть несколько способов сделать это. Один из способов — разделить MyConfiguration
на множество мелких кусочков, чтобы вместо myConfiguration.getConfig("x")
вы делали @Inject @Configuration("x") String
или что-то в этом роде. В качестве альтернативы вы можете сделать сам MyConfiguration
инжектируемым, а затем предоставить ему методы доступа для частей. Правильный ответ в некоторой степени зависит от типа данных, которые вы пытаетесь смоделировать — сделайте зависимости слишком мелкими, и ваши привязки могут стать сложными в обслуживании (хотя есть способы сделать это лучше); сделайте зависимости слишком грубыми, и вы усложните тестирование (например: что проще, предоставить только конфигурацию «x», которая нужна тестируемому классу, или создать конфигурацию всего приложения?).
Вы даже можете сделать оба:
/** Annotates a configuration value. */
@BindingAnnotation
@Retention(RetentionPolicy.RUNTIME)
public @interface Config {
String value();
}
/** Installs bindings for {@link MyConfiguration}. */
final class MyConfigurationModule extends AbstractModule {
@Override protected void configure() {}
@Provides
@Singleton
MyConfiguration provideMyConfiguration() {
// read MyConfiguration from disk or somewhere
}
@Provides
@Config("x")
String provideX(MyConfiguration config) {
return config.getConfig("x").getName();
}
}
// elsewhere:
/** The main application. */
final class MyApplication {
private final String xConfig;
@Inject MyApplication(@Config("x") String xConfig) {
this.xConfig = xConfig;
}
// ...
}
Вы можете использовать аналогичный подход в модульных тестах:
/** Tests for {@link MyApplication}. */
@RunWith(JUnit4.class)
public final class MyApplicationTest {
// Note that we don't need to construct a full MyConfiguration object here
// since we're providing our own binding, not using MyConfigurationModule.
// Instead, we just bind the pieces that we need for this test.
@Bind @Config("x") String xConfig = "x-configuration-for-test";
@Before public void setUp() {
// See https://github.com/google/guice/wiki/BoundFields
Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this);
}
@Inject MyApplication app;
@Test public void testMyApp() {
// test app here
}
}
Внедрение зависимостей также поощряет другую передовую практику, которую я очень рекомендую, а именно разработку системы типов таким образом, чтобы недопустимые состояния не представлялись (в максимально возможной степени). Если вся конфигурация, необходимая MyApplication
, передается в его конструктор, невозможно получить объект MyApplication
, который не имеет допустимой конфигурации. Это позволяет вам «заранее загружать» инварианты вашего класса, что значительно упрощает рассмотрение поведения ваших объектов.
Наконец, примечание о Injector.getInstance()
. В идеале вы используете Injector
ровно один раз в своей программе: сразу после ее создания. То есть вы должны иметь возможность делать Guice.createInjector(...).getInstance(MyApplication.class).start()
и никогда нигде не хранить ссылку на Injector
. Я обычно создаю приложения, используя абстракцию Guava ServiceManager
(см. также < href="https://stackoverflow.com/questions/18502650/how-to-use-guava-servicemanager-with-guice-injection">этот вопрос), поэтому единственное, что мне нужно сделать является:
public static void main(String[] args) throws Exception {
Injector injector = Guice.createInjector(...);
ServiceManager manager = injector.getInstance(ServiceManager.class);
manager.startAsync().awaitHealthy();
}
person
Daniel Pryden
schedule
26.02.2015