Что мы на самом деле понимаем, когда говорим о DI Framework? Примеры с простыми шаблонами проектирования и переход на внедрение зависимостей с помощью Spring!

Вопрос. Что такое внедрение зависимостей? О. Внедрение зависимостей – это метод, при котором служба получает другие службы, от которых она зависит. Учитывая это простое определение, давайте посмотрим на пример!

Это некоторые классы и определение интерфейса, у нас есть 2 средства проверки, одно для английского и одно для французского языков, которые реализуют простой интерфейс Checker.

Класс EmailService зависит от проверки, чтобы отправить электронное письмо.

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

Что, если мы хотим отправить электронное письмо на французском языке? Конечно, нам нужно использовать FrenchChecker. Но как мы можем изменить зависимость checker в нашем уже созданном объекте. Единственное решение — Reflection. Ну, когда у вас есть проблема и вы хотите использовать отражение для решения этой проблемы, у вас будет две проблемы. Так что это четкий сигнал о том, что ваш дизайн неверен. Конечно, вы можете переключиться в конструкторе EmailService на FrenchChecker, но просто подумайте, что вы получаете библиотеку EmailService в виде JAR и не можете изменить исходный код.

Что мы можем сделать?

Как разработчик нашей новой службы электронной почты, мы можем изменить конструктор, чтобы он получал зависимость во время создания, как здесь:

Итак, теперь наш EmailService достаточно гибок, чтобы принимать любые реализации Checker, даже новые, созданные пользователем нашей небольшой библиотеки. Если вы не заметили, мы уже сделали внедрение зависимостей. Как сказано в определении DI, мы просто вручную внедрили checker, который является зависимостью от EmailService.

Но в чем проблема с этим новым дизайном? Нам, как клиентам EmailService, необходимо знать внутренние зависимости почтового сервиса. Просто подумайте об экстремальном сценарии со 100 зависимостями, от которых может зависеть служба, и мы, как пользователи этой службы, должны решить, какие зависимости использовать. Это явное нарушение концепции инкапсуляции. Нам, как простым пользователям службы, необходимо вручную создавать все зависимости. Это называется графом зависимостей, и нам не нужно заботиться о них как о клиентах одного сервиса. Так что эту проблему должен решать кто-то другой. До того, как фреймворк Dependency Injection появился на свет, существовал какой-то способ управления созданием этого сервиса и внедрением зависимостей. Одним из первых является шаблон проектирования Factory.

Для этого, как разработчики EmailService, мы предоставляем нашим клиентам простой способ, которым они могут использовать наш сервис, без необходимости думать о внутреннем устройстве нашего сервиса. Если некоторым людям нужен FrenchEmailer, им просто нужно вызвать наш фабричный метод newFrenchEmailer(), и все, им не нужно думать о том, какой Checker использовать, или какие-либо другие зависимости в случае, если наш обслуживание зависит от чего-то другого.

Конечно, с этим подходом есть некоторые проблемы, иначе зачем нам DI-фреймворк :)? Проблема в том, что это статическая форма создания, если, например, некоторые клиенты захотят разработать новый Checker для китайского языка, им потребуется создать новый метод Factory, но, учитывая, что наша библиотека распространяется в виде JAR, они могут не меняйте наш код без нерелигиозных методов (таких как отражение). Таким образом, пользователи, если хотят расширить наш сервис, должны вернуться к классическому способу создания и управления зависимостями вручную.

Другой, более сложный шаблон проектирования, используемый для управления зависимостями, — Service Locator. Это распространенный шаблон проектирования в мире Java EE, и его можно рассматривать как родителя инфраструктур внедрения зависимостей.

Для этого нам нужен класс Context с методом поиска:

Нам также нужен ServiceCache с методами get и put.

И, наконец, класс ServiceLocator, который возвращает зависимость из кеша, если она присутствует, а если нет, то создаст ее с помощью контекстного поиска.

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

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

Представляем весну

Прежде чем говорить о Spring Framework, я хочу поговорить о том, что означает фреймворк. Фреймворк — это форма инверсии управления. Подумайте о фреймворке как о месте, где вы ничем не можете управлять, у вас нет основного метода, вы не знаете, где начинается и заканчивается ваш код, у вас просто есть несколько мест, где вы можете разместить свои код, и кто-то другой использует этот код, когда считает это нужным. Подумайте о веб-сервере, вы на самом деле не управляете или не знаете об обработке запроса и о том, что он создаст новый поток для нового запроса или использует один из пула потоков. У вас просто есть интерфейс сервлета с четким соглашением о том, что вы получаете запрос и возвращаете ответ, и место, где вы связываете этот сервлет с конечной точкой URL. Обсуждение IoC должно быть ясным с точки зрения того, какие именно элементы управления у нас отняты.

Прежде чем углубиться, давайте посмотрим на живой пример того, что такое Spring на вкус. Отказ от ответственности: то, что я покажу вам в следующем разделе, это устаревшая форма использования Spring, но это понятнее, чем новый способ, который на самом деле не нов или это.

Да, я знаю, это XML. Некоторые люди привыкли так жить. Вы можете игнорировать первые 7 строк и сосредоточимся на теге ‹bean›. Что такое бин? Компонент — это объект, которым управляет Spring. Не более того. Вы можете видеть декларативный способ, которым мы говорим Spring, что нам нужен EmailService, который зависит от средства проверки, и способ, которым мы ссылаемся на эту зависимость. Итак, мы объявляем 2 bean-компонента: bean-компонент checker, который будет создан из класса EnglishChecker, и emailService, который создается из класса EmailService, и его зависимость внедряется через конструктор. Для этого мы должны создать Context, помните это слово из обсуждения ServiceLocator.

Как видите, мы создаем Context на основе файла spring.xml, а после этого вызываем метод getBean(), чтобы получить наш bean-компонент. Мы получаем наш сервис таким же образом, как и в случае с ServiceLocator, небезопасным способом, используя ключ String, который мы объявили в файле .xml, или используя безопасный способ, используя ключ .class, без необходимости приведения. Что касается обсуждения IoC, я хочу прояснить, что у нас нет смысла использовать оператор new, как в случае с Factory, ServiceLocator или ручным внедрением зависимостей. Таким образом, в этом случае контроль, который у нас забирается, — это создание и связывание сервисов и их зависимостей. Мы только что объявили, что нам нужно и как должно выглядеть граф зависимостей, и фреймворк решает, как и когда создавать эти объекты, то есть бины. Например, вы можете видеть, что в файле xml мы сначала объявили EmailService, а затем Checker. При этом я хочу прояснить, что мы не можем влиять на порядок создания bean-компонентов, размещая выше в файле объявлений, Spring должен решить, когда их создавать, на основе графа зависимостей.

В начале знакомства со Spring я сказал вам, что этот способ использования XML является устаревшим. После того, как в Java 5 была представлена ​​функция аннотаций, в разработке и использовании Spring дела пошли лучше, и вместе с Java Configuration мы смогли полностью избавиться от XML-файлов. Итак, давайте посмотрим, как это работает сейчас.

Прежде всего нам нужно объявить наши бины. Аннотация @Service, похожая на аннотацию @Component, сообщает Spring, что это должно управляться само по себе. Таким образом, это эквивалентно объявлению тега bean в XML.

EmailService, как вы знаете, зависит от чекера, но ранее мы объявили 2 чекера с аннотацией @Service, поэтому нам нужно явно указать, когда мы внедряем в конструктор, какой из них нам нужен, английский или французский, и для этого мы используем @Квалификатор Аннотация. Мы можем использовать аннотацию @Conditional для более динамичного выбора, какую из них использовать, но я не хочу вдаваться в подробности. Чтобы эта аннотация что-то значила для Spring, нам нужно объявить пустой класс с аннотациями @Configuration и @ComponentScan и создать контекст.

Заключительные слова

Что я хочу прояснить, так это то, как Spring управляет созданием bean-компонентов и компонентов, и как это забирается у нас из-за идеи инверсии управления. Используя этот стандартный способ получения всех наших сервисов и зависимостей, давайте сосредоточимся на развитии бизнеса, а не на связывании сервисов и управлении зависимостями. Я показал вам ручное управление зависимостями с некоторыми хорошо известными шаблонами проектирования, их преимуществами и недостатками, а также некоторые основы использования Spring. Эта статья не предназначена для изучения основ Spring, она предназначена для того, чтобы дать глубокие знания об идее внедрения зависимостей.