Что такое inversion of control и как spring реализует этот принцип

Обновлено: 04.07.2024

Какие методы реализации принципа Инверсия управления (IoC) вы знаете?

  • Шаблон «Фабрика» (англ. Factory pattern)
  • Локатор служб
  • Внедрение зависимости (англ. Dependency injection)
  • Контекстный поиск (англ. contextualized lookup)

Какими способами можно реализовать «внедрение зависимостей» в Java?

Что такое Spring?

Spring Framework (или коротко Spring) - универсальный фреймворк с открытым исходным кодом для Java-платформы. Центральной частью Spring является контейнер Inversion of Control, который предоставляет средства конфигурирования и управления объектами Java с помощью рефлексии. Контейнер отвечает за управление жизненным циклом объекта: создание объектов, вызов методов инициализации и конфигурирование объектов путём связывания их между собой.

Spring имеет множество дочерних под проектов, в том числе:

  • Data - для работы с хранилищами данных.
  • MVC - для создания веб приложений.
  • Boot - для быстрой компоновки и создания приложения на основе других Spring проектов.
  • Cloud - для создания распределённой приложений.

Какие основные отличия в версиях Spring?

  • Версия 3 (2009) - Поддержка Java 5 (annotations, generics, varargs, . ).
  • Версия 4 (2016) - Поддержка Java 8 (lambda, stream api, . ).
  • Версия 5 (2017) - Построен на основе Reactive Streams .

В чем разница между Inversion of Control и Application Context?

IoC — инверсия управления. Вместо ручного внедрения зависимостей, фреймворк забирает ответственность за это. ApplicationContext — реализация IoC в Spring. Bean Factory — это базовая версия IoC контейнера Application Context также включает дополнительные функции, которые обычно нужны для разработки корпоративных приложений

В чем различие между web.xml и the Spring Context - servlet.xml?

web.xml — Метаданные и конфигурация любого веб-приложения, совместимого с Java EE. Java EE стандарт для веб-приложений. servlet.xml — файл конфигурации, специфичный для Spring Framework.

Сравните Application Context, IoC Container, vs Web Container и EJB Container. Нужен ли Web Container для запуска Spring Boot приложения?

Web Container и EJB Containers являются частью приложения/веб-сервера, таких как Tomcat, Websphere, Weblogic. Они добавляют свою дополнительную функциональность к ним. Java EE определяет контракт для веб-приложений, эти контейнеры являются реализацией этих контрактов.

Spring контейнер может являться частью любого приложения, которое вы делаете на java. Spring может работать внутри веб-контейнера, ejb контейнера или даже без них.

Как происходит запуск IoC-контейнера Spring?

  1. Парсинг конфигурации и создание BeanDefinition - конфигурация с помощью XNL, аннотаций, JavaConfig.
  2. Настройка созданных BeanDefinition - на данном этапе происходит настройка еще не созданных бинов через классы, реализующие BeanFactoryPostProcessor . Например, PropertySourcesPlaceholderConfigurer
  3. Создание кастомных FactoryBean - FactoryBean — это generic интерфейс, которому можно делегировать процесс создания бинов.
  4. Создание экземпляров бинов - созданием экземпляров бинов занимается BeanFactory при этом, если нужно, делегирует это кастомным FactoryBean . Экземпляры бинов создаются на основе ранее созданных BeanDefinition .
  5. Настройка созданных бинов - Интерфейс BeanPostProcessor позволяет вклиниться в процесс настройки ваших бинов до того, как они попадут в контейнер. Интерфейс несет в себе несколько методов.

Какие способы конфигурирование Spring существуют?

XML конфигурация — ClassPathXmlApplicationContext(“context.xml”) . Используется класс — XmlBeanDefinitionReader , который реализует интерфейс BeanDefinitionReader . Тут все достаточно прозрачно. XmlBeanDefinitionReader получает InputStream и загружает Document через DefaultDocumentLoader . Далее обрабатывается каждый элемент документа и если он является бином, то создается BeanDefinition на основе заполненных данных (id, name, class, alias, init-method, destroy-method и др.). Каждый BeanDefinition помещается в Mindmap. Mindmap хранится в классе DefaultListableBeanFactory .

Аннотация/JavaConfig — с указанием пакета для сканирования — AnnotationConfigApplicationContext(“package.name”) или через аннотации с указанием класса (или массива классов) помеченного аннотацией @Configuration - AnnotationConfigApplicationContext(JavaConfig.class) . Внутри AnnotationConfigApplicationContext , то можно увидеть два поля.

ClassPathBeanDefinitionScanner сканирует указанный пакет на наличие классов помеченных аннотацией @Component (или любой другой аннотацией которая включает в себя @Component ). Найденные классы разбираются и для них создаются BeanDefinition. Чтобы сканирование было запущено, в конфигурации должен быть указан пакет для сканирования. @ComponentScan() или <context:component-scan base-package="package.name"/>

  • Groovy конфигурация — GenericGroovyApplicationContext(“context.groovy”) . Данная конфигурация очень похожа на конфигурацию через Xml, за исключением того, что в файле не XML, а Groovy. Чтением и анализом groovy конфигурации занимается класс GroovyBeanDefinitionReader.

Что предпочитаете использовать для конфигурации Spring?

Предпочитаю аннотации, если кодовая база хорошо описывается такими элементами, как @Service , @Component , @Autowired Однако когда дело доходит до конфигурации, у меня нет каких-либо предпочтений. Я бы оставил этот вопрос команде.

Что такое BeanPostProcessor ?

Интерфейс BeanPostProcessor позволяет вклиниться в процесс настройки ваших бинов до того, как они попадут в контейнер. Интерфейс несет в себе несколько методов.

Оба метода вызываются для каждого бина. У обоих методов параметры абсолютно одинаковые. Разница только в порядке их вызова. Первый вызывается до init-метода, второй, после. Важно понимать, что на данном этапе экземпляр бина уже создан и идет его дополнительная настройка. Тут есть два важных момента:

  • Оба метода в итоге должны вернуть бин. Если в методе вы вернете null, то при получении этого бина из контекста вы получите null, а поскольку через BeanPostProcessor проходят все бины, после поднятия контекста, при запросе любого бина вы будете получать фиг, в смысле null.
  • Если вы хотите сделать прокси над вашим объектом, то имейте ввиду, что это принято делать после вызова init метода, иначе говоря это нужно делать в методе postProcessAfterInitialization .

Процесс дополнительной настройки показан на рисунке ниже. Порядок в котором будут вызваны BeanPostProcessor не известен, но мы точно знаем что выполнены они будут последовательно.

Для чего нужен Component Scan?

Первый шаг для описания Spring Beans это добавление аннотации — @Component , или @Service , или @Repository . Однако Spring ничего не знает об этих бинах, если он не знает где искать их. То, что скажет Spring где искать эти бины и называется Component Scan. В @ComponentScan вы указываете пакеты, которые должны сканироваться. Spring будет искать бины не только в пакетах для сканирования, но и в их подпакетах.

В чём отличие между @Component и @ComponentScan ?

@Component помечает класс в качестве кандидата для создания Spring бина. @ComponentScan указывает где Spring искать классы, помеченные аннотацией @Component или его производной

Для чего используется аннотация @Bean ?

В классах конфигурации Spring, @Bean используется для определения компонентов с кастомной логикой.

В чём разница между @Bean и @Component ?

@Bean используется в конфигурационных классах Spring. Он используется для непосредственного создания бина. @Component используется со всеми классами, которыми должен управлять Spring. Когда Spring видит класс с @Component , Spring определяет этот класс как кандидата для создания bean.

В чём разница между @Component , @Service и @Repository аннотациями?

Все они определяют бины Spring. Однако между ними всё же есть разница.

@Component — универсальный компонент @Repository — компонент, который предназначен для хранения, извлечения и поиска. Как правило, используется для работы с базами данных. @Service — фасад для некоторой бизнес логики

Пользовательские аннотации, производные от @Component, могут добавлять специальную логику в бинах. Например, бины, получившиеся при помощи @Repository, дополнительно имеют обработку для JDBC Exception

Можем ли мы использовать @Component вместо @Service для бизнес логики?

Если @Component является универсальным стереотипом для любого Spring компонента, то @Service в настоящее время является его псевдонимом. Однако в официальной документации Spring рекомендуется использовать именно @Service для бизнес логики. Вполне возможно, что в будущих версиях фреймворка, для данного стереотипа добавится дополнительная семантика, и его бины станут обладать дополнительной логикой.

Можем ли мы применить @Autowired с не сеттерами и не конструкторами методами?

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

В чем разница между Сквозной Функциональностью (Cross Cutting Concerns) и АОП (аспектно ориентированное программирование)?

Сквозная Функциональность — функциональность, которая может потребоваться вам на нескольких различных уровнях — логирование, управление производительностью, безопасность и т.д. АОП — один из подходов к реализации данной проблемы

Почему возвращаемое значение при применении аспекта @Around может потеряться? Назовите причины.

Метод, помеченный аннотацией @Around, должен возвращать значение, которое он (метод) получил из joinpoint.proceed()

Как вы решаете какой бин внедрить, если у вас несколько подходящих бинов. Расскажите о @Primary и @Qualifier ?

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

Если все бины имеют одинаковый приоритет, мы всегда будем использовать @Qualifier

Если бин надо выбрать во время исполнения программы, то эти аннотации вам не подойдут. Вам надо в конфигурационном классе создать метод, пометить его аннотацией @Bean , и вернуть им требуемый бин.

Как вы добавите Component Scan в Spring Boot?

@SpringBootApplication определяет автоматическое сканирование пакета, где находится класс Application.

Какие возможности предоставляет аннотация @Controller ?

@Controller - уточнение интерфейса @Component . Экземпляр класса, помеченного такой аннотация, также становиться бином в Spring. Используется для указание точек взаимодействия с приложением в Spring MVC. Обычно используется с аннотацией @RequestMapping .

В чём разница между @Controller и @RestController ?

@RestController = @Controller + @ResponseBody

@RestController превращает помеченный класс в Spring-бин. Этот бин для конвертации входящих/исходящих данных использует Jackson message converter. Как правило целевые данные представлены в json или xml.

Как мы можем выбрать подходящий бин при помощи application.properties?

и два компонента

Тогда в application.properties добавим свойство application.greeting: real

Воспользуемся данным решением:

Почему иногда мы используем @ResponseBody , а иногда ResponseEntity ?

ResponseEntity необходим, только если мы хотим кастомизировать ответ, добавив к нему статус ответа. Во всех остальных случаях будем использовать @ResponseBody .

Для @ResponseBody единственные состояния статуса это SUCCESS(200), если всё хорошо и SERVER ERROR(500), если произошла какая-либо ошибка.

В чем разница между Filters, Listeners and Interceptors?

Фильтры и перехватчики делают по сути одно и тоже: они перехватывают какое-то событие, и делают что-то до или после. Java EE использует термин Filter, Spring называет их Interceptors. Именно здесь AOP используется в полную силу, благодаря чему возможно перехватывание вызовов любых объектов.

В чем разница между ModelMap и ModelAndView ?

Model — интерфейс, ModelMap его реализация. ModelAndView является контейнером для пары, как ModelMap и View. Обычно я люблю использовать ModelAndView. Однако есть так же способ когда мы задаем необходимые атрибуты в ModelMap, и возвращаем название View обычной строкой из метода контроллера.

В чем разница между model.put() и model.addAttribute() ?

Метод addAttribute отделяет нас от работы с базовой структурой hashmap. По сути addAttribute это обертка над put, где делается дополнительная проверка на null. Метод addAttribute в отличие от put возвращает modelmap.

Что можете рассказать про Form Binding?

Нам это может понадобиться, если мы, например, захотим взять некоторое значение с HTML страницы и сохранить его в БД. Для этого нам надо это значение переместить в контроллер Spring. Если мы будем использовать Spring MVC form tags, Spring автоматически свяжет переменные на HTML странице с бином Spring.

Почему мы используем Hibernate Validator?

Hibernate Validator никак не связан с БД. Это просто библиотека для валидации. Hibernate Validator версии 5.x является эталонной реализацией Bean Validation 1.1

Где должны располагаться статические (css, js, html) ресурсы в Spring MVC приложении?

Расположение статических ресурсов можно настроить. В документации Spring Boot рекомендуется использовать /static, или /public, или /resources, или /META-INF/resources.

Введение в Spring

SpringFramework Spring Framework представляет собой набор готовых решений для использования всех основных Enterpise Java технологий — JDBC, ORM, JTA, Servlets/JSP, JMX и многих других. Абстрактные классы, фабрики и бины разработаны таким образом, чтобы программисту оставалось написать только свою логику.

Spring также содержит прекрасные классы, реализующие паттерн MVC (модель-вид-контроллер) для Web-приложений. Абстрактные DAO-классы упрощают работу с persistance layer, будь то JDBC, JDO или Hibernate, и позволяют избежать больших трудностей при смене технологии. Транзакции реализуются собственным Transaction API, который позволяет использовать различные менеджеры транзакций, в том числе JTA J2EE-сервера приложений и Hibernate. Не стоит думать, что Spring является просто IoC-контейнером, он также предоставляет фундаментальную библиотеку для использования известных Java-технологий


Рисунок 5. Возможности IoC-контейнера Spring

Простой пример

Рассмотрим особенности IoC-контейнера Spring. Сам контейнер представляет собой реализацию интерфейса BeanFactory. Обычно используется реализация XmlBeanFactory, которая читает runtime-конфигурацию из XML-файла.

Этот простой фрагмент XML создает в контейнере bean с именем helloWorld, и устанавливает его свойство message в Sergei. Реализация может быть следующей:

Создание объектов

В приведенном примере за конструирование объекта helloWorld отвечает контейнер – атрибут class элемента bean соответствует вызову конструктора без параметров. Spring поддерживает широкий спектр механизмов создания объектов – вызов конструктора с параметрами или без, использование фабрик классов или фабричных методов. Использование всех этих методов довольно прямолинейно, единственным нюансом является то, что при использовании конструктора с параметрами необходимо указывать либо тип, либо индекс параметра конструктора (атрибуты type и index соответственно).

При использовании статического фабричного метода не гарантируется, что созданный объект будет того же класса, где определен фабричный метод.

В элементе constructor-arg поддерживаются те же значения, что и в элементе property, полный их синтаксис будет рассмотрен далее.

В описаниях bean'ов могут быть заданы два режима работы – режим прототипа (используется по умолчанию) и режим синглтона (единственный экземпляр). В первом режиме каждый запрос объекта с заданным именем будет возвращать новый объект с указанными свойствами и связями, во втором режиме будет создан единственный экземпляр, и он будет возвращаться при каждом вызове к BeanFactory, независимо от контекста. Это поведение регулируется атрибутом singleton элемента bean.

Значение singleton по умолчанию – true.

Установка зависимостей и свойств компонентов

Возникает вопрос: "Какие типы данных могут быть установлены при помощи элемента property?", - ответ: "Любые". Spring использует технологию JavaBeans для установки свойств объектов, эта технология частично используется в JSP для установки свойств типа String и примитивных типов, но в Spring она используется гораздо шире – интерфейс PropertyEditor позволяет устанавливать значения свойств любых типов. Поддерживаются стандартные коллекции из java.util: List, Set, Map, Properties, а также ссылки на объекты в контейнере по имени и значение null.

  • java.util.Properties – задается элементом props, отдельные свойства добавляются при помощи вложенного элемента prop, где атрибут key задает имя свойства, а текст внутри – значение.
  • java.util.Map – задается элементом map, отдельные элементы добавляются при помощи вложенного элемента entry, где атрибут key задает имя свойства, а значение – это значение внутреннего элемента. Внутренний элемент entry может представлять любой объект, в том числе и Map.
  • java.util.List, java.util.Set – представляются элементами list и set соответственно. Каждый внутренний элемент представляет собой значение элемента списка (множества).
  • ссылка на объект из контейнера – представляется элементом ref, причем ссылка на объект, определенный в том же файле, использует атрибут local, а в любом из конфигурационных файлов – bean.
  • Значение null соответствует литералу null.

Жизненный цикл объектов

Контейнер Spring поддерживает методы инициализации и разрушения объектов. В любом объекте, реализующем InitializingBean, после задания значений всех декларированных свойств будет автоматически вызван метод afterPropertiesSet. На самом деле реализация этого интерфейса не обязательна, Spring предоставляет возможность вызова любого метода, указанного в атрибуте init-method в определении bean-а. Аналогичная ситуация и с методом разрушения, который вызывается при разрушении контейнера, интерфейс и атрибут называются DisposableBean и destroy-method соответственно.

Другие возможности

В этой статье кратко описаны основные возможности контейнера Spring, за более подробным описанием возможностей можно обратиться к Spring Reference Documentation, замечу лишь, что Spring активно использует не только объектно-ориентированный подход, но и аспектно-ориентированный. Аспекты и замещения методов могут быть также легко прописаны в конфигурационном файле, как и bean. Таким образом, контейнер Spring предоставляет широкий спектр опций соединения компонентов в систему, и позволяет разработчику сосредоточиться на разработке переносимых компонентов с использованием ООП и развивающейся АОП. Огромным преимуществом этого контейнера перед другими является набор классов, позволяющих использовать современные Java/J2EE технологии совместно с классами вашей предметной области.

Инверсия управления/Inversion of Control

Инверсия управления является распространенным явлением, с которым вы столкнетесь при использовании фреймворков. И действительно, она часто рассматривается как определяющая характеристика фреймворка.

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

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

Однако если бы я использовал оконную систему для чего-то похожего, я написал бы что-то, что работает с окном:

Теперь между этими двумя программами большая разница в потоке управления — в частности, в управлении временем, когда вызываются методы process_name и process_quest. В примере с коммандной строкой я контролирую, когда эти методы вызываются, но в примере с оконным приложением нет. Вместо этого я передаю контроль оконной системе (команда Tk.mainloop). Далее она решает, когда вызвать мои методы, основываясь на связях, которые я настроил при создании формы. Управление инвертировано — управляют мной, а не я управляю фреймворком. Это явление и называется инверсией управления (также известно как Принцип Голливуда — «Не звони нам, мы сами позвоним тебе» — Hollywood Principle — «Don't call us, we'll call you»).

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

Ральф Джонсон и Брайан Фут.

Инверсия управления является ключевой частью того, что различает фреймворк и библиотеку. Библиотека это по существу набор функций, которые вы можете вызывать, в наши дни они организованы в классы. Каждый вызов выполняет некоторую работу и возвращает управление обратно пользователю.

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

Существуют различные способы подключить ваш код для его дальнейшего вызова. В предыдущем примере на ruby, мы вызываем метод bind текстового поля, который принимает имя события и замыкание в качестве аргументов. Всякий раз, когда текстовое поле узнает о событии, оно вызывает наш код из замыкания. Использование таких замыканий очень удобно, но многие языки не поддерживают их.

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

EJB-компоненты являются хорошим примером такого стиля инверсии управления. При разработке сессионного компонента(session bean), вы можете реализовать различные методы, которые вызываются EJB-контейнером в различных точках/состояниях жизненного цикла. Например, у интерфейса SessionBean есть методы ejbRemove, ejbPassivate (сохранен во вторичное хранилище) и ejbActivate (восстановлен из пассивного состояния). Вы не можете управлять вызовом этих методов, только тем, что они делают. Контейнер вызывает нас, а не мы вызываем его.

Примечание перевода, пример:

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

Примечание перевода, пример:

В наши дни, в связи с ростом количества IoC-контейнеров, существует некоторая путаница со смыслом инверсии управления. Некоторые люди путают общий принцип с конкретными стилями инверсии управления (такими как внедрение зависимостей), которые эти контейнеры используют. Все это немного запутанно (и иронично), так как IoC-контейнеры, как правило, рассматриваются в качестве конкурента EJB, но EJB использует инверсию управления.

Этимология: Насколько я могу судить, термин инверсии управления впервые появился на свет в работе Джонсона и Фута Designing Reusable Classes, опубликованной в журнале Object-Oriented Programming в 1988 году. Работа является одной из тех, что в возрасте хороши — ее можно прочитать даже спустя пятнадцать лет. Они считают, что они взяли этот термин откуда-то еще, но не могут вспомнить откуда. Затем термин втерся в объектно-ориентированное сообщество и вновь появился в книге Gang of Four. Более красивый синоним «Принцип Голливуда», кажется, берет начало в работе Ричарда Свита в Mesa в 1983 году. В списке целей разработки он пишет: Не звони нам, мы сами позвоним тебе (Закон Голливуда): инструмент должен организовать Тахо, чтобы предупредить его, когда пользователь захочет передать какое-то событие инструменту, вместо того, чтобы принимать модель «запросить у пользователя команду и выполнить ее». Джон Влиссидес пишет колонку о C++, которая несет в себе хорошее объяснение концепции под названием «Принцип Голливуда». (Спасибо Брайану Футу и Ральфу Джонсону за помощь с этимологией).

Реализация своего IoC контейнера

image

Каждый начинающий разработчик должен быть знаком с понятием Inversion of Control (Инверсия управления).

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

Инверсия управления (Inversion of Control, IoC) — важный принцип объектно-ориентированного программирования, используемый для уменьшения связанности в компьютерных программах и входящий в пятерку важнейших принципов SOLID.

На сегодня существуют несколько основных фреймворков по этой теме:

По сей день пользуюсь Spring и частично доволен его функционалом, но пора бы попробовать что-то и свое, не правда ли?

О себе

Зовут меня Никита, мне 24 года, и я занимаюсь java (backend) на протяжении 3 лет. Обучался только на практических примерах, параллельно пытаясь разобраться в спеках классов. На данный момент работаю (freelance) — написание CMS для коммерческого проекта, где и использую Spring Boot. Недавно посетила мысль — «Почему бы не написать свой IoC (DI) Container по своему видению и желанию?». Грубо говоря — «Захотелось своего с блекджеком. ». Об этом и пойдет сегодня речь. Что ж, прошу под кат. Ссылка на исходники проекта.

Особенности

  1. Поля класса
  2. Конструктор класса
  3. Функции класса (стандартный сеттер на один параметр)

— ленивая инициализация компонентов (по требованию);

— встроенные файлы конфигурации загрузчика (форматы: ini, xml, properties);

— обработчик аргументов командной строки;

— обработка модулей путем создания фабрик;

— встроенные события и слушатели;

— встроенные информаторы (Sensibles) для «информирования» компонента, фабрики, слушателя, процессора (ComponentProcessor) о том, что определенная информация должна быть загружена в объект в зависимости от информера;

— модуль для управления / создания пула потоков, объявление функций как исполняемых задач в течение некоторого времени и инициализация их на фабрике пулов, а также начиная с параметров SimpleTask.

Используется Reflections API стороннего разработчика со стандартным сканером.


Получаем коллекцию классов с помощью фильтров аннотаций, типов.
В данном случаи это @IoCComponent, @Property и прородитель Analyzer<R, T>

1) В первую очередь происходит инициализация конфигурационных типов.

2) Инициализация анализаторов
3) Инициализация найденных компонентов (Классы помеченные аннотацией @IoCComponent)

3) Методы сканирования
— метод инъекции параметров в конструктор класса

— метод инъекции параметров в поля класса

— метод инъекции параметров через функции класса

1. ComponentProcessor — утилита позволяющая изменять компонент по собственному желанию как до его инициализации в контексте так и после.

4. Слушатели (Listener's)
Реализован функционал слушателей, гарантировано multi-threading выполнение с настройкой рекомендуемого количества дескрипторов для оптимизированной работы событий.

** Пояснения:
Функция dispatch(Event) — главная функция обработчик событий системы.
— Присутствуют стандартные реализации слушателей с проверкой на типы событий а так же со встраиваемыми пользовательскими фильтрами . Стандартные фильтры входящие в пакет: AndFilter, ExcludeFilter, NotFilter, OrFilter, InstanceFilter (custom). Стандартные реализации слушателей: FilteredListener и TypedListener. Первый задействует в себе фильтр для проверки входящего объекта события. Второй осуществляет проверку событийного объекта либо же любого другого на принадлежность к определенному инстансу.

1) Модуль для работы с потоковыми задачами в Вашем приложении


— маркер-аннотация для включения модуля в контекст (@ThreadingModule)


— внедрение фабрики модуля в инсталлируемый компонент приложения

*Пояснения:
ThreadFactorySensible — один из дочерних классов-информаторов для внедрения в инстанциируемый компонент опр. информации (конфигурации, контекста, модуля, etc.).
DefaultThreadingFactory — фабрика модуля threading-factory.

Аннотация @SimpleTask — параметризируемый маркер-аннотация для выявления у компонента реализации задач в функциях. (запускает поток с заданными параметрами аннотацией и добавляет его в фабрику, откуда его можно будет достать и, к примеру, отключить выполнение).

— стандартные функции шедулинга задач


***Обратите внимание, что ресурсы в пуле запланированных потоков ограничены, и задачи должны выполняться быстро.

— дефолтная конфигурация пула

Стартовая точка или как это все работает

Подключаем зависимости проекта:


Тестовый класс приложения.


**Пояснения:
Аннотация @ScanPackage — указывает контексту, какие пакеты следует сканировать для идентификации компонентов (классов) для их инъекции. Если пакет не указан, будет сканироваться пакет класса, помеченного этой аннотацией.

Дополнительно создадим несколько классов-компонентов для непосредственной проверки функционала.

Запускаем средствами Вашей IDE или командной строкой проект.

+ имеется встроенное апи парсинга конфигурационных файлов (ini, xml, properties).
Обкатанный тест лежит в репозитории.

Будущее

В планах расширять и поддерживать проект на сколько это будет возможно.

Что я хочу видеть:

  1. Написание дополнительных модулей — сетевые/работа с базами данных/написание решений типовых задач.
  2. Замена Java Reflection API на CGLIB
  3. etc. (прислушиваюсь к пользователям, если таковые будут)

Всем спасибо. Надеюсь кому-то мои труды пригодятся.
UPD. Обновление статьи — 15.09.2018. Релиз 1.0.0

Концепция Inversion of Control и основы Spring

Паттерны уменьшения зависимостей между компонентами системы

J2EE – большая и сложная спецификация, охватывающая, тем не менее, далеко не все нюансы реализации. Кроме того, многие реализации серверов приложений содержат возможности, выходящие за рамки спецификации. Разработка под конкретный сервер приложений вольно или невольно приводит к тому, что код приложения включает участки, зависимые от этого сервера. Это создает немало проблем при попытке переноса приложения под другой сервер. Spring переносим между разными серверами приложений, и поддерживает WebLogic, Tomcat, Resin, JBoss, WebSphere и другие серверы приложений.

Концепция, лежащая в основе инверсии управления, часто выражается "голливудским принципом": "Не звоните мне, я вам сам позвоню". IoC переносит ответственность за выполнение действий с кода приложения на фреймворк. В отношении конфигурирования это означает, что если в традиционных контейнерных архитектурах наподобие EJB, компонент может вызвать контейнер и спросить: "где объект Х, нужный мне для работы?", то в IoC сам контейнер выясняет, что компоненту нужен объект Х, и предоставляет его компоненту во время исполнения. Контейнер делает это, основываясь на подписях методов (таких, как свойства JavaBean) и, возможно, конфигурационных данных в формате XML.

В этой статье будут рассмотрены основные паттерны ослабления связей между компонентами системы, а также использование паттерна IoC в Sping Framework.

Для рассмотрения паттернов вынесения зависимостей будем использовать простой фрагмент кода, где класс LoginManager использует класс UserList, чтобы получить доступ к списку активных пользователей системы.

Единственное тонкое место в этом коде – это то, что LoginManager зависит от UserList. У такой зависимости можно отметить следующие недостатки:

  • Если захочется каким-то образом изменить способ хранения пользователей, например, использовать базу данных или LDAP, придется переписать LoginManager, чтобы он создавал соответствующий класс для работы со списком пользователей.
  • Если предположить, что класс UserList зависим от платформы, например, использует JNI или пользуется каким-либо API, специфичным для какой-то платформы, то LoginManager будет также платформенно-зависимым.

Таким образом, из-за зависимости компонентов страдает переносимость и возможность их повторного использования.

Рассмотрим способы выхода из этой неприятной ситуации. Необходимо сделать так, чтобы класс LoginManager мог работать с любым хранилищем пользовательской базы. Для этого определим такие хранилища, как UserStorage.

public class UserList implements UserStorage

Такая организация обладает гораздо большей гибкостью. Чтобы класс был действительно универсальным, достаточно добавить setUserStorage(UserStorage theStorage) в LoginManager. Любые способы хранения пользователей могут быть легко добавлены. Этот способ открывает дорогу для создания тестов отдельно для классов UserList и LoginManager. В случае с LoginManager может быть использован класс-заглушка MockUserStorage.


Рисунок 1. Начальная диаграмма классов.


Рисунок 2. Диаграмма классов с вынесением зависимости.

Итак, мы имеем прекрасные переносимые компоненты LoginManager, UserList, JdbcUserStorage, LdapUserStorage. Не стоит думать, что мы избавились от необходимости соединять их вместе. Для использования этих компонентов необходим некий класс RuntimeAssembler, который будет делать грязную работу по соединению компонентов в единую систему.

RuntimeAssembler-классы не предназначены для повторного использования или наследования от них. В больших системах эти классы максимально запутаны. IoC-контейнеры как раз и предназначены для упрощения соединения компонентов. Все они позволяют использовать API и создавать RuntimeAssember-классы, при этом XML-конфигурация системы и RuntimeAssembler не понадобятся.

Альтернативой паттерну вынесения зависимости (Dependency Injection) является паттерн Service Locator. Он широко используется в J2EE. Так, ServiceLocator может инкапсулировать все JNDI-вызовы J2EE-системы или создание (получение) UserStorage-реализации в нашем примере. Основным отличием Dependency Injection и Service Locator является то, что в Service Locator для получения реализации UserStorage используется вызов объекта ServiceLocator (см. код ниже), в то время как в Dependency Injection ассемблер создает связь автоматически.


Рисунок 3. Диаграмма для Dependency Injection.


Рисунок 4. Диаграмма для Service Locator.

Очевидно, что в паттерне ServiceLocator есть зависимость между LoginManager и ServiceLocator, в то время как LoginManager не зависит от RuntimeAssembler в Dependency Injection. Каждый может выбирать один из этих IoC-паттернов, в зависимости от своих нужд и задач. Далее будет рассмотрен IoC-контейнер, реализованный в Spring Framework.

Читайте также: