Статьи

Вы реализовали main () неправильно все это время

С самых ранних дней Java (и C-подобных языков в целом) канонический способ запуска вашей программы был примерно таким:

public class A {
  public static void main(String[] args) {
    new A().run(args);
  }

  public void run(String[] args) {
    // Your application starts here
  }
}

Если вы все еще делаете это, я здесь, чтобы сказать вам, что пора остановиться.

Отпустить «новое»

Сначала установите Guice в свой проект:

<dependency>
  <groupId>com.google.inject</groupId>
   <artifactId>guice</artifactId>
   <version>3.0</version>
</dependency>

а затем измените ваш основной метод следующим образом:

public class A {
  public static void main(String[] args) {
    Injector.getInstance(A.class).run(args);
  }
}

Итак, что это покупает именно вас?

Вы найдете много статей, объясняющих различные преимущества Guice, такие как возможность замены различных сред на лету, но я собираюсь использовать другой угол в этой статье.

Давайте начнем с предположения о существовании класса Config, который содержит различные параметры конфигурации. Я просто покажу их жестко и использую поля, чтобы уменьшить класс:

public class Config {
  String host = "com.example.com";
  int port = 1234;
}

Этот класс является одноэлементным, он создается где-то в вашем основном классе и не используется нигде в данный момент. Однажды вы понимаете, что вам нужен этот экземпляр в другом классе, который находится глубоко в вашей иерархии времени выполнения, которую мы будем называть Deep. Например, если вы поставите точку останова в методе, где вам нужен этот объект конфигурации, ваш отладчик покажет вам стеки фреймов, подобные этому:

com.example.A.main()
com.example.B.f(int, String)
com.example.C.g(String)
com.example.Deep.h(Foo, int)

Простой и неправильный способ решения этой проблемы — сделать экземпляр Config статическим для некоторого класса (возможно, A) и обращаться к нему напрямую из Deep. Я надеюсь, что мне не нужно объяснять, почему это плохая идея: вы не только хотите избежать использования статики, но вы также хотите убедиться, что каждый объект открыт только для объектов, которые в них нуждаются, и сделать Статический объект конфигурации сделает ваш экземпляр видимым для всей базы кода. Не очень хорошая вещь.

Вторая мысль — передать объект в стек, чтобы вы изменили все сигнатуры следующим образом:

com.example.A.main()
com.example.B.f(int, String, Config)
com.example.C.g(String, Config)
com.example.Deep.h(Foo, int, Config)

Это немного лучше, поскольку вы строго ограничили доступ к объекту Config, но учтите, что вы по-прежнему делаете его доступным для большего числа методов, чем действительно нужно: B # f и C # g на самом деле не имеют ничего общего с этим объектом, и небольшой укус дискомфорта поражает вас, когда вы начинаете писать Javadoc:

public class C {
  ...
  /**
   * @param config This method doesn't really use this parameter,
   * it just passes it down so Deep#h can use it.
   */
  public void g(String s, Config config) {

Ненужное разоблачение на самом деле не является худшей частью этого подхода, проблема в том, что он изменяет все эти сигнатуры по пути, что, безусловно, нежелательно в частном API и абсолютно разрушительно в публичном API. И, конечно, это абсолютно не масштабируемо: если вы продолжаете добавлять параметр в ваш метод всякий раз, когда вам нужен доступ к определенному объекту, вы скоро будете иметь дело с методами, которые принимают десять параметров, большинство из которых они просто передают по цепочке.

Вот как мы решаем эту проблему с помощью внедрения зависимостей (выполняется в этом примере Guice, но это применимо к любой библиотеке, которая реализует JSR 330 , очевидно):

public class Deep {
  @Inject
  private Config config;

и мы сделали. Вот и все. Вам не нужно никоим образом изменять класс Config, а также не нужно вносить какие-либо изменения в любой из классов, отделяющих Deep от вашего основного класса. Благодаря этому вы также минимизировали доступ объекта Config к классу, который в этом нуждается.

Впрыскивая право

Существуют различные способы внедрения объекта в ваш класс, но я просто упомяну два, которые, я думаю, являются наиболее важными. Я только что показал «инъекцию поля» в предыдущем абзаце, но учтите, что вы также можете использовать «инъекцию в конструктор»:

public class Deep {
  private final Config config;

  @Inject
  public Deep(Config config) {
    this.config = config;
  }

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

Лично я не вижу особого смысла в конечных полях, поскольку я почти никогда не сталкивался с ошибкой, вызванной случайным переназначением поля, поэтому я стараюсь использовать инъекцию поля всякий раз, когда могу.

Взяв его на следующий уровень

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

Эта тема, вероятно, будет охватывать несколько глав книги, посвященной внедрению зависимостей, поэтому я просто подведу итог: не все объекты могут быть внедрены таким образом, и одно из преимуществ использования инфраструктуры внедрения зависимостей в вашем коде состоит в том, что Вы должны думать о том, к какой категории жизненного цикла относятся ваши объекты. Сказав это, если вы хотите узнать, как Guice может внедрять объекты, которые создаются в более позднее время в жизненном цикле вашего приложения, найдите Javadoc для класса Provider .

Завершение

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