Каждый начинающий Java-разработчик, который уже изучил азы Java SE, рано или поздно приходит к тому, что, собственно эта самая Java SE уже никому не нужна. Все работодатели требуют Java EE, а наиболее распространенным фреймворком здесь уже давным-давно является Spring. И хотя различных статей, обучающих уроков на сегодняшний день уже достаточно много, все же разобраться со Spring крайне сложно. В этой статье я попытаюсь объяснить самые основы Spring. Я не буду описывать пример создания простого приложения на Spring, поскольку таких примеров существует огромное количество, моя цель — объяснить наиболее простым языком, как устроен Spring и зачем он нужен.


Итак, Spring — многоцелевой фреймворк. Это означает, что с его помощью вы можете решать огромное количество типичных задач. Центральной частью Spring является контейнер «Inversion of control». Скорее всего, вы уже сталкивались с понятием «контейнер», когда изучали Java SE. В широком смысле слова контейнер - это программный компонент, который может хранить в себе экземпляры класса. Например, класс «Panel» в библиотеке для создания графического интерфейса «Swing». Мы можем создать экземпляр этого класса, а потом добавить туда кнопки, полосы прокрутки и другие элементы пользовательского интерфейса. Это и есть не что иное, как контейнер. Inversion of control - точно такой же контейнер, только он хранит экземпляры класса, необходимые для работы Вашего приложения, использующего Spring. Зачем это нужно? Ответ на этот вопрос может показаться достаточно сложным человеку, который только начинает свой путь программиста. Я бы настоятельно порекомендовал Вам ознакомиться с GRASP-паттернами. С одной стороны это поможет значительно улучшить качество Вашего кода, с другой стороны именно эти паттерны отвечают на ранее поставленный вопрос. Я постараюсь привести лишь один пример, который должен немного прояснить ситуацию. Представьте, что вы пишите свое приложение и создали два класса: «A», «B». При этом для работы методов класса «A» вам необходим экземпляр класса «B». Следовательно, в методах класса «A» мы с помощью оператора «New» создаем экземпляры класса «B». Отлично, наше приложение заработало, однако, через какое-то время класс «B» был удален, его функционал возложен на класс «С» либо класс «B» просто необходимо изменить. Теперь все не так хорошо, поскольку нам приходится менять и класс «A». И так каждый раз… А теперь представьте себе, что ваших классов не 2-3, а несколько тысяч и цепочка таких «изменений» может быть практически бесконечна (причем фактически требовалось изменить лишь один класс). Поэтому программисты стараются избегать ссылок на внешние классы, в этом и состоит суть GRASP-паттерна «Low coupling». Как же этого добиться? Spring предлагает очень простое решение…


Скорее всего, Вы уже перевели на русский язык «Inversion of control» и получили «передача управления». Пока не совсем понятно, о чем речь, но все на самом деле просто. Spring предлагает нам возложить на него обязанности по созданию и управлению необходимыми нам объектами. Объект, который создан и управляется контейнером Spring, называется «Bean». В широком смысле слова «Bean» - не что иное, как экземпляр класса, у которого существует конструктор по умолчанию, геттеры и сеттеры. Почему? Ответ простой: это позволяет рефлексивно создать экземпляр такого класса, после чего установить значение его полей или прочитать это значение. Если Вам непонятно слово «рефлексивно», то суть состоит в том, что контейнер Spring изначально не знает, экземпляры каких классов необходимо создать. Поэтому он не может использовать оператор «New». И именно поэтому используется специальный механизм создания экземпляров классов, основанный на «Reflection API», однако, последнее не является предметом этой статьи, поэтому останется на самостоятельное изучение. Важно понимать, что у каждого такого «Bean» есть имя - его уникальный идентификатор, по которому «Bean» можно найти в контейнере Spring. Имя «Bean» вы можете задать самостоятельно, если вы этого не сделаете, то имя «Bean» будет совпадать с именем экземпляра Вашего класса (например, у вас есть класс «А», значит имя «Bean», присвоенное ему по умолчанию, будет «а»). Для того, что бы лучше понять, как работает контейнер Spring, достаточно представить себе его в виде обычной коллекции типа «ключ-значение», например, Map <String, Object>, где ключом будет имя «Bean» (String), а значением - экземпляр Вашего класса (Object). Теперь, зная имя «Bean», вы без труда можете получить сам экземпляр Вашего класса.


Итак, у нас есть класс с конструктором по умолчанию, геттерами и сеттерами, как же теперь заставить Spring создать экземпляр этого класса? Сначала Вы должны создать свой класс, но только такой, который может быть «Bean». Теперь нужно сделать так, чтобы Spring узнал о том, что вы хотите передать контроль над этим классом ему. Это можно сделать несколькими способами. Самый старый способ - указать этот класс в xml-файле конфигурации. Я не буду останавливаться на этом, ведь примеров более чем достаточно. Еще один способ - аннотации. Существует несколько аннотаций, которые можно проставить над именем класса, чтобы дать указание Spring создать в контейнере экземпляр этого класса. Основные такие: «Component», «Repository» (служит для создания репозиториев для работы с базами данных), «Service» (служит для создания различных сервисов, к примеру, сервиса работы с базами данных), «Controller» (служит для создания веб-контроллеров). Аннотация «Component» находится на вершине этой иерархии. Этой аннотацией можно пометить, например, сервис и он будет работать. Зачем же тогда нужна аннотация «Service», если ее можно заменить на «Component»? Ее назначение в большей степени состоит в том, чтобы Ваш код было легче читать. Пока этого достаточно, ведь мы пытаемся разобраться в основах Spring. Хочу рассказать про еще один интересный способ создания «Bean». Состоит он в том, что мы можем создать специальный метод, который нужно пометить аннотацией «Bean» (по умолчанию именем такого «Bean» будет название самого метода), в котором мы опишем логику создания какого-либо экземпляра нашего класса. Соответственно, наш метод будет возвращать экземпляр нашего класса, процедура создания которого описана в методе. Преимуществом такого подхода является то, что в методе мы можем описать любую логику создания экземпляра класса (например, мы можем создавать различные имплементации одного и того же интерфейса, помещая экземпляры класса в переменную типа указанного интерфейса, в зависимости от файла конфигурации). Необходимо также понимать, что такие методы могут быть созданы в специальном классе, помеченном аннотацией «Configuration». Эта аннотация нужна для того, чтобы указать Spring, что данный класс является конфигурационным, т. е. в нем описано, как создавать «Bean» и где они расположены.


Теперь у нас есть свой класс, он помечен аннотацией «Component». Замечательно, но как же теперь этим воспользоваться? Очень просто. Над переменной типа этого класса достаточно разместить аннотацию «Autowired». Теперь в эту переменную Spring автоматически запишет созданный и размещенный в его контейнере «Bean».


Хочу отметить, что после каждой из аннотаций «Bean», «Component», «Repository», «Service», «Controller» в скобках и кавычках мы можем указать желаемое имя «Bean» в контейнере Spring. К примеру @Bean («myClass»). Теперь, независимо от названия класса, имя его в контейнере Spring будет именно «myClass». Причем я бы настоятельно рекомендовал поступать именно так, а не полагаться на автоматическое именование «Bean». Все дело в том, что в дальнейшем кто-либо может при рефакторинге переименовать ваш класс. Если В вашем коде существует обращение к экземпляру этого класса по имени «Bean» (чуть позже я объясню, как это делать), то вы получите исключение, ведь имя «Bean» в контейнере изменилось (поменялось имя класса), а обращение по имени осталось неизменным (указано старое имя класса).

Процесс инициализации переменной с помощью контейнера Spring носит имя «Dependency injection».


Нам удалось заставить контейнер Spring работать, но мы не получили ответ на вопрос, как же это поможет нам избавиться от зависимостей одного класса от другого? Здесь на помощь приходят интерфейсы. Аннотацию «Autowired» следует ставить не над переменной типа класса («Bean»), а над переменной типа интерфейса, который указанный класс имплементирует. Теперь в наших классах имеются лишь ссылки на интерфейсы, ссылки на другие классы отсутствуют. Вот и все. Теперь мы можем изменить класс как угодно либо удалить его, создав новый. Главное, чтобы у нас имелась имплементация указанного интерфейса.

Помимо аннотации «Autowired», Вы можете инициализировать свои переменные в классе с помощью Spring, используя обычный конструктор. Например, у нас есть 3 класса, которые являются «Bean»: «A», «B», «C», при этом в классе нам необходимо использовать экземпляр класса «B», а в классе «B» - экземпляр класса C. Тогда можно сделать так:

@Component(«myClassA»)

public class A {

               @Autowired

               @Qualifier(«myClassB»)

               private B b;

}

@Component(«myClassB»)

public class B {

               private C c;

               public B (C c){

                          this.c=c;

               }

}

@Component(«с»)

public class C {

                          ...

}


Вот пример инициализации переменных с помощью аннотации «Autowired» и конструктора. Класс «А» использует конструктор для инициализации переменной «с», при этом в классе «А» используется аннотация «Autowired» для инициализации переменной «b». Обратите внимание, что у класса «B» отсутствует конструктор по умолчанию (хотя ранее я говорил, что это обязательное условие). На самом деле в этом конкретном случае класс «C» является компонентом Spring, т.е. является «Bean» (помечен аннотацией «Component»), соответственно, его экземпляр может быть создан Spring автоматически. Именно это произойдет при вызове конструктора класса «B» - экземпляр класса «С» будет создан автоматически (это и есть инициализация с помощью конструктора). Это не сработает, если класс «С» не будет компонентом.

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


Скорее всего, вы обратили внимание на аннотацию «Qualifier». Ранее я обещал показать, как заставить Spring обратиться к «Bean» по его имени. Именно это делает аннотация «Qualifier». Бывают ситуации, когда Вы не сможете обойтись без этой полезной аннотации. Все дело в том, что в случаях, когда у интерфейса имеется всего одна имплементация, Spring поместит туда нужный «Bean» самостоятельно. Ситуация меняется, когда у интерфейса имеется несколько имплементаций. Именно тогда на помощь придет аннотация «Qualifier». Именно с ее помощью вы укажите контейнеру Spring какую из имплементаций интерфейса нужно извлечь из контейнера поместить в переменную типа интерфейса в конкретном случае.


Последнее необходимое условие для того, чтобы ваше приложение, основанное на Spring, работало корректно, вам необходимо не выходить за пределы контекста Spring. Что это значит? Это значит, что при создании экземпляра вашего класса с помощью оператора «New» все переменные, помеченные аннотацией «Autowired» проинициализированы не будут просто потому, что контейнер Spring ничего не знает об экземпляре вышеуказанного класса. Разумеется, вы можете создавать некоторые из экземпляров ваших классов с помощью оператора «New», но это к Spring уже не имеет никакого отношения.


В завершении статьи хочется сказать несколько слов про Spring Boot. Для начала еще раз отмечу, что Spring - многоцелевой фреймворк. Он содержит множество компонентов (Data (JPA) - работа с базами данных, AOP — реализация аспекто-ориентированной парадигмы программирования, основан на AspectJ, WEB (MVC) — web-разработка и.т.д.). Все эти компоненты предназначены для решения конкретных задач. Spring Boot, в свою очередь, предназначен для того, чтобы максимально быстро выбрать и объединить нужные компоненты Spring в проект. При этом Spring Boot сам запишет нужные зависимости и конфигурации в соответствующий файл для «Maven» или «Gradle» по Вашему желанию. Кроме того, Spring Boot содержит множество настроек по умолчанию, которые позволят запустить Ваше приложение максимально быстро. Теперь все, что Вам нужно, - зайти на сайт https://start.spring.io/ и попробовать создать свой первый проект на Spring.

 

Other articles that can be interesting for you