С самых ранних дней 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 .