К основному контенту

IoC-контейнер изнутри

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

Очевидно, что зависимости необходимо некоторым образом описать: объект, называемый так-то, скажем A, зависит от других объектов, например B и C. При этом контейнер ничего не знает о том, какой из объектов мы называем A, а какие — B и C. Это тоже необходимо описать. Контейнер оперирует только теми компонентами, которые он знает (часто их называют бинами). Для создания объекта контейнеру необходимо также знать к какому классу объектов он принадлежит и как долго его помнить. Самый простой способ предоставить все эти данные (с точки зрения устройства контейнера, конечно) — напрямую обратиться к регистрационному методу контейнера, передав ему в качестве параметра некоторый объект с метаданными. Второй вариант: промаркировать наш код некоторым образом, понятным контейнеру. (Стоит помнить, что это приводит к зависимости нашего кода от используемого контейнера.) В этом случае у контейнера должен быть сканер, который найдет весь маркированный код и составит из маркировки и кода необходимое описание. Пользователю контейнера может быть удобно задать метаданные в некотором внешнем источнике и тогда контейнеру необходим компонент, который сможет прочитать метаданные из этого источника. Итого, контейнеру необходим транслятор, который примет от пользователя описание компонентов и их зависимостей и переведёт его во внутреннее представление контейнера.

Компоновка производится путём внедрения одних объектов в другие. Внедрение может происходить как во время создания объекта (зависимости передаются в конструктор) так и после него (зависимости передаются в сеттеры или другие сконфигурированные методы). Внедряться может как один объект так и некоторое их множество (например, все объекты, реализующие определённый интерфейс). Зависимости могут быть обязательными и необязательными. Вариантов внедрения не так уж много и их удобно описать набором стратегий разрешения и внедрения зависимостей.

Имея описание компонентов, их зависимостей и стратегии их внедрения можно переходить к собственно созданию объектов, для чего контейнеру понадобится фабрика. В большинстве случаев создание объекта сводится к вызову его конструктора и сеттеров, в которые передаются ранее созданные зависимости. Дополнительно может вызываться сконфигурированный метод инициализации. Иногда возникает необходимость сконструировать объект определённым способом (например, используя шаблон “строитель”). В этом случае пользователь описывает / регистрирует в контейнере свою фабрику, которой основная фабрика контейнера будет делегировать создание объекта в описанных случаях.

В зависимости от указанного в метаданных времени жизни объект после создания помещается в реестр объектов с таким же временем жизни (реестр - условное название, в коде конкретной реализации ориентируйтесь на scope). Контейнер обращается за объектом сначала в соответствующий реестр, а потом, если нужный объект отсутствует, к фабрике. Объекты находятся в реестре до его закрытия / уничтожения. Контейнер может предоставлять возможность пользователю задать метод объекта, который вызовется перед тем как объект будет забыт.

Надеюсь, эта статья демистифицирует IoC-контейнер в ваших глазах. Я намеренно не привязывался к какой-либо конкретной реализации контейнера и предполагаю, что в деталях вы сможете разобраться сами. В учебных целях могу порекомендовать посмотреть на IoC-контейнер Petite, который входит в состав легковесного фреймворка Jodd. Он достаточно функциональный, но проще того, который предоставляется Spring фреймворком.


Комментарии

Популярные сообщения из этого блога

Одно приложение, несколько баз данных

Рецепт от Spring Boot Некоторое время назад мне довелось писать агрегатор информации, разбросанной по нескольким базам данных с разными схемами. Для реализации был выбран Spring Boot. Ну, потому что модный и судя по примерам существенно упрощает жизнь за счет умной автоконфигурации. В этой статье я опишу, что же необходимо сконфигурировать и как, в случае, если вы отошли от стандартного сценария. Первым делом, необходимо прописать настройки доступа к каждой из баз. Например, вот так: Следующим шагом создадим отдельный класс конфигурации (для удобства), в котором определим dataSources: Обратите внимание, как просто получить настройки с помощью @ConfigurationProperties. Правда, пришлось ввести вспомогательный класс BaseDataSourceProperties — наследник DataSourceProperties, в котором область видимости метода getDriverClassName расширена до public. И осталось совсем немного — сконфигурировать JPA-репозитории. Насчет немного я, конечно, пошутил :) В этой части предстоит больше ...

Обработка изображений с ImageMagick

ImageMagick ( http://www.imagemagick.org ) — набор утилит для создания, редактирования, конвертирования и просмотра растровых изображений. Графический режим необходим только для просмотра. Для остальных действий над изображениями достаточно консоли. То есть налицо два отличия от привычных редакторов растровых изображений (вроде GIMP или Krita): использование набора утилит вместо одной программы для операций над изображениями не требуется GUI. Очевидно, что таким инструментом вряд ли будут пользоваться художники, фотографы или дизайнеры. Чтобы разобраться для кого предназначен этот набор, предлагаю ознакомиться с предоставляемыми возможностями. Что умеет ImageMagick? Чтобы ответить на поставленный вопрос я перечислю входящие в набор утилиты, напишу какой функционал предоставляет каждая из них и, конечно же, приведу примеры использования. identify — информационная утилита, воспользовавшись которой можно узнать формат изображения и множество других его свойств (например, высоту,...

Изучение Qt маленькими порциями. Разбираем ваше первое приложение

Прошлый раз вы установили QtCreator, а затем сгенерировали ваше первое приложение. Всё сработало и у вас теперь есть собственное пустое окно QMainWindow на экране. Всё хорошо, но что же действительно было сделано? Давайте пройдёмся по сгенерированному коду, чтобы стало понятно, что же случилось на самом деле. Приложение состоит из одного класса и функции main, необходимой для запуска. Давайте посмотрим на эту функцию main.cpp #include <QtGui/QApplication> #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } В этой функции создаётся экземпляр класса QApplication . Каждому приложению Qt нужен один и только один экземпляр класса QApplication. Он представляет собой само приложение и содержит главный цикл обработки событий. Вы можете спросить, что такое цикл обработки событий? Это просто цикл, ожидающий наступления события. Например, нажатие клавишы, перемещение мышки, сетевые пакеты, событ...