Spring - это самый распространенный фреймворк для написания Java приложений. Первая версия вышла еще в далеком 2003 году, после чего Spring претерпел очень много изменений и стал по-настоящему монструозным.
Это повлекло разбиение его на множество отдельных модулей, например:
- Spring Core - ядро фреймворка, на чем держатся все остальные модули.
- Spring Boot - это тот же Spring, просто с возможностью авто-конфигурирования модулей.
- Spring Test - каждое приложение должно быть хорошо покрыто разными уровнями тестирования, и этот модуль предоставляет функционал для этого, поднимая Spring Context и сразу добавляя необходимые зависимости в проект.
- Spring Data - модуль для работы с самыми распространенными базами данных, как реляционными, так и нереляционными (NoSQL).
- Spring Web - написание веб приложений, в текущий момент времени все приложения общаются друг с другом по сети, поэтому Web - это неотъемлемая часть изучения каждого разработчика.
- Spring Security - работа по сети заставляет улучшать авторизацию и аутентификацию в приложении, повышать его безопасность, а значит необходимо готовое решение из коробки, что и предоставляет нам Spring в виде модуля Security.
Каждый последующий раздел будет рассматривать части и целое таких разделов Spring-a, как:
- Spring Core;
- Spring Test;
- Spring Web;
- Spring Security;
- Spring Boot;
- Spring Data;
- Spring Validation;
- Spring AOP;
В папке DOC sql-скрипты и др. полезные файлы (статьи по IOC и DI).
Для начала проведем предварительную подготовку:
Шаг 1 - в файле build.gradle добавим необходимые нам зависимости:
/* Подключим Spring-core и Spring-context. */
implementation 'org.springframework:spring-core:5.3.22'
implementation 'org.springframework:spring-context:5.3.22'
Spring Framework — универсальный фреймворк с открытым исходным кодом для Java-платформы.
Spring Framework обеспечивает комплексную модель разработки и конфигурации для современных бизнес-приложений на Java — на любых платформах. Ключевой элемент Spring — поддержка инфраструктуры на уровне приложения: основное внимание уделяется взаимосвязей отдельных частей бизнес-приложений, поэтому разработчики могут сосредоточиться на бизнес-логике без лишних настроек в зависимости от среды исполнения.
Spring Framework может быть рассмотрен как коллекция меньших фреймворков или фреймворков во фреймворке. Большинство этих фреймворков может работать независимо друг от друга, однако они обеспечивают большую функциональность при совместном их использовании. Эти фреймворки делятся на структурные элементы типовых комплексных приложений:
- Inversion of Control-контейнер: конфигурирование компонентов приложений и управление жизненным циклом Java-объектов.
- Фреймворк аспектно-ориентированного программирования: работает с функциональностью, которая не может быть реализована возможностями объектно-ориентированного программирования на Java без потерь.
- Фреймворк доступа к данным: работает с системами управления реляционными базами данных на Java-платформе, используя JDBC- и ORM-средства и обеспечивая решения задач, которые повторяются в большом числе Java-based environments (окружения на основе Java).
- Фреймворк управления транзакциями: координация различных API управления транзакциями и инструментарий настраиваемого управления транзакциями для объектов Java.
- Фреймворк MVC: каркас, основанный на HTTP и сервлетах, предоставляющий множество возможностей для расширения и настройки (customization).
- Фреймворк удалённого доступа: конфигурируемая передача Java-объектов через сеть в стиле RPC, поддерживающая RMI, CORBA, HTTP-based протоколы, включая web-сервисы (SOAP).
- Фреймворк аутентификации и авторизации: конфигурируемый инструментарий процессов аутентификации и авторизации, поддерживающий много популярных и ставших индустриальными стандартами протоколов, инструментов, практик через дочерний проект Spring Security (ранее известный как Acegi).
- Фреймворк удалённого управления: конфигурируемое представление и управление Java-объектами для локальной или удалённой конфигурации с помощью JMX.
- Фреймворк работы с сообщениями: конфигурируемая регистрация объектов-слушателей сообщений для прозрачной обработки сообщений из очереди сообщений с помощью JMS, улучшенная отправка сообщений по стандарту JMS API.
- Тестирование: каркас, поддерживающий классы для написания модульных и интеграционных тестов.
Все рассмотренные вопросы данной части имеют практическое и понятийное значение для дальнейшего понимания работы экосистемы Spring-а. Некоторые уроки не имеют практической реализации в коде Java (3 и 8), но имеют краткое описание.
Зависимости объектов см. DOC/ObjectDependencies.jpg (UserService зависит от UserRepository, который зависит от Connection pool).
-
ClassDependenciesDemo.java - пример зависимости объектов;
-
IoCDemo.java - пример инверсии контроля (IoC);
-
DOC/DI - что такое внедрение зависимостей;
-
DOC/IOC - что такое инверсия контроля;
-
DOC/SpringAppScheme - схема классического Spring приложения и способы конфигурирования Bean Definition;
Создаем в папке ресурсов application.xml и уже там будем настраивать наши Bean-ы (Bean Definition).
Примеры, как настраивать Bean Definition при различных видах полей в конструкторах классах (чертежах) будущих bean-ов.
Внедрение ссылки на bean в поле другого bean-a:
<constructor-arg index="1" name="login" type="java.lang.String" ref="login"/>
Внедрение ссылки на bean в List другого bean-a:
<list>
<value>--arg1=value1</value>
<value>--arg2=value2</value>
<ref bean="driver"/>
</list>
Внедрение ссылки на bean в Map другого bean-a:
<map>
<entry key="url" value="postgresurl"/>
<entry key="password" value="123"/>
<entry key="driver" value-ref="driver"/>
</map>
Поскольку все bean-ы по-умолчанию singleton-ы, то ссылки во всех случаях в пределах одного контекста будут на один и тот же объект (bean).
Допустим у нас FirmRepository.java зависит от AdvancedConnectionPool.java имеет приватный конструктор. В resources/application.xml мы прописываем эту зависимость:
<bean id="firmRepository" class="spring.oldboy.repository.FirmRepository" factory-method="of">
<constructor-arg ref="pool3"/>
</bean>
- FactoryInjectionDemo.java - демо приложение для отображения взаимодействия этих классов, без явного создания и прописи зависимостей в самом приложении (т.е. создали классы, в *.xml прописали связь, пользуемся).
Класс SetterInConnectionPool.java имеет одно не final поле, вот его мы и сможем задавать через сеттер. В файле resources/application.xml создадим bean definition id="poolSetIn" в котором мы имеем возможность задать содержимое нашего Map, как через конструктор, так и через сеттер (properties):
<!-- Через конструктор передаем null -->
<constructor-arg index="4" name="properties" type="java.util.Map">
<null/>
</constructor-arg>
<!-- Задаем те же параметры через сеттер (как property), что передавали через конструктор -->
<property name="properties">
<map>
<entry key="url" value="postgresurl"/>
<entry key="password" value="123"/>
<entry key="driver" value-ref="driver"/>
</map>
</property>
- SetterInjectionDemo.java - демо приложение для изучения варианта инъекции зависимости через сеттеры.
- Singleton Scope - Когда мы определяем bean-компонент как singleton (обычно это default - по-умолчанию), контейнер создает один экземпляр этого bean-компонента. Все запросы для этого имени компонента будут возвращать один и тот же объект, который кэшируется. Любые изменения объекта будут отражены во всех ссылках на компонент. Эта область видимости является значением по умолчанию, если не указана другая область.
В итоге при запросе singleton bean-a spring берет наши bean definition и прогоняет их через всю цепочку жизненного цикла DOC/BeansLifeCycle/BeansLifeCycle.png и далее кеширует полученный bean и возвращает его на любой запрос.
- Prototype Scope - Bean с областью действия prototype (прототип) будет возвращать другой экземпляр каждый раз, когда он запрашивается из контейнера. Он определяется установкой прототипа значения в аннотацию @Scope в определении bean-компонента (или в resources/application.xml, как у нас).
В данной ситуации spring также обращается к bean definition и снова прогоняет bean по LifeCycle цепочке и отдает его по требованию, но не кеширует, а каждый раз повторяет цикл.
Пример работы разной области видимости: PrototypeBeansDemo.java (в application.xml bean с id="firmService" имеет scope="prototype")
Spring поставляется с различными вариантами вызова метода инициализации. В общем, нам нужно инициализировать некоторые ресурсы во время создания bean-a. Мы можем добиться этого (см. DOC/BeansLifeCycle/LifeCycleCallbacks.jpg):
- аннотация @PostConstruct;
- внедрение интерфейс org.springframework.beans.factory.InitializingBean в наш Spring bean;
- использование *.init() метода и настройки resources/application.xml;
Мы можем выбрать удобный нам способ, однако, рекомендуется использовать аннотацию. Но для демонстрации мы применим *.init() метод.
Как говорится, что создано рано или поздно будет уничтожено. Мы инициализируем наши ресурсы во время создания bean-компонента Spring. И естественно нам нужно закрыть все открытые ресурсы непосредственно перед уничтожением этого bean-компонента, весь код, связанный с очисткой, будет частью метода destroy. Мы можем добиться этого, тремя способами:
- аннотация @PreDestroy;
- реализовав интерфейс org.springframework.beans.factory.DisposableBean в нашем Spring bean-е;
- использование *.destroy() метода и настройки resources/application.xml;
Попробуем всего помаленьку.
См. resources/application.xml:
<bean id="initCallBackPool" name="intCbPool" class="spring.oldboy.pool.InitCallBackPool"
init-method="init"
destroy-method="destroy"
scope="singleton">
PreDestroy или Destruction CallBacks вызываются ТОЛЬКО у Singleton-bean-ов (НЕ у Prototype), т.к. у IoC контейнера нет ссылки на prototype bean-ы !!!
Естественно лучше использовать только одну из методик вызова обратных вызовов (или аннотация - предпочтительно, или наследование интерфейса, или методы *.init(), *.destroy())
Для использования такой возможности нам нужен файл из которого будут браться свойства, создадим: application.properties.
Теперь в application.xml нам нужно указать ссылку на него, но в виде bean-a:
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:application.properties"/>
</bean>
Однако, для упрощения жизни разработчикам есть еще вариант с использованием контекста:
<context:property-placeholder location="classpath:application.properties"/>
И теперь нам доступен Expression Language (EL) — скриптовый язык выражений, который позволяет получить доступ к Java компонентам (JavaBeans) из JSP. Начиная с JSP 2.0 используется внутри JSP тегов для отделения Java кода от JSP для обеспечения лёгкого доступа к Java компонентам. Как пример см. https://github.com/JcoderPaul/HTTP_Servlets_Java_EE.
Но, Spring разработчики пошли дальше и создали Spring Expression Language (кратко - "SpEL") – это мощный язык выражений, который поддерживает запросы и манипуляции с графом объектов во время выполнения программы. И теперь он нам доступен. Синтаксис языка похож на EL, но предлагает дополнительные возможности, в первую очередь вызов методов и базовые функции шаблонизации строк.
Хотя существует несколько других языков выражений Java - OGNL, MVEL, JBoss EL и другие - язык выражений Spring Expression Language был создан для того, чтобы дать сообществу Spring единый язык выражений с надлежащей поддержкой, который можно использовать во всех продуктах портфеля Spring.
Язык выражений поддерживает следующую функциональность:
- Литеральные выражения (литералы);
- Булевы операторы и операторы отношений;
- Регулярные выражения;
- Класс-выражения;
- Доступ к свойствам, массивам, спискам и ассоциативным массивам;
- Обращение к методам;
- Операторы отношений;
- Присваивание;
- Вызов конструкторов;
- Ссылки на бины;
- Построение массива;
- Встраиваемые списки;
- Встраиваемые ассоциативные массивы;
- Тернарный оператор;
- Переменные;
- Определяемые пользователем функции;
- Проекция коллекций;
- Выборка коллекций;
- Шаблонные выражения.
Документация:
- https://docs.spring.io/spring-framework/docs/3.0.x/reference/expressions.html
- https://docs.spring.io/spring-framework/reference/
Например (см. application.xml):
<constructor-arg index="4" name="properties" type="java.util.Map">
<map>
<entry key="url" value="postgresurl"/>
<entry key="password" value="123"/>
<entry key="driver" value="#{driver.substring(3)}"/>
<entry key="test" value="#{driver.length() > 10}"/>
<entry key="test1" value="#{driver.length() > T(Math).random() * 10}"/>
<entry key="hosts" value="#{'${db.hosts}'.split(',')}"/>
<entry key="currentUser" value="#{systemProperties['user.name']}"/>
<entry key="currentUser" value="${user.name}"/>
</map>
</constructor-arg>
Точка расширения - org.springframework.beans.factory.config.BeanFactoryPostProcessor. Семантика этого интерфейса аналогична семантике BeanPostProcessor, с одним существенным отличием: BeanFactoryPostProcessor работает с конфигурационными метаданными bean-ов. То есть IoC-контейнер Spring позволяет BeanFactoryPostProcessor считывать конфигурационные метаданные и потенциально изменять их до того, как контейнер создаст экземпляры каких-либо bean-ов, помимо экземпляров BeanFactoryPostProcessor.
Можно сконфигурировать несколько экземпляров BeanFactoryPostProcessor, а также контролировать порядок запуска этих экземпляров BeanFactoryPostProcessor, установив свойство order. Однако мы можем установить это свойство только в том случае, если BeanFactoryPostProcessor реализует интерфейс Ordered. Если мы пишем свой собственный BeanFactoryPostProcessor, то следует рассмотреть реализацию интерфейса Ordered или PriorityOrder.
Пост-процессор фабрики bean-ов автоматически запускается, если он объявляется внутри ApplicationContext, для применения изменений к конфигурационным метаданным, определяющим контейнер. Spring содержит ряд предопределенных постпроцессоров фабрики bean-ов, такие, как PropertyOverrideConfigurer и PropertySourcesPlaceholderConfigurer. Также можно использовать специальный BeanFactoryPostProcessor, например, для регистрации специальных редакторов свойств.
ApplicationContext автоматически определяет все развернутые в нем beam-ы, которые реализуют интерфейс BeanFactoryPostProcessor. Он использует эти bean-ы в качестве постпроцессоров фабрики bean-в в соответствующий момент. Мы можем развернуть эти bean-ы постпроцессора так же, как и любые другие bean-ы.
- bean_factory_pp - папка с фабричными пост-процессорами, для простой демонстрации.
- BeanFactoryPPDemo.java - демо-приложение, который позволяет увидеть, как меняя возвращаемый объект пост-процессорного метода *.getOrder() можно изменить порядок вызова набора пост-процессоров.