Spring in Action Covers Spring 5.0 перевод на русский. Глава 1

Начало работы со Spring

Хотя греческий философ Гераклит не был хорошо известен как разработчик программного обеспечения, он, казалось, хорошо разбирался в этом вопросе. Его цитируют так: "единственное неизменное-это перемены.” Это заявление отражает основополагающую истину разработки программного обеспечения.

То, как мы разрабатываем приложения сегодня, отличается от того, что было год назад, 5 лет назад, 10 лет назад, и, конечно, 15 лет назад, когда первоначальная структура Spring Framework была представлена в книге рода Джонсона, Expert One-on-One J2EE Design and Development (Wrox, 2002, http://mng.bz/oVjy).

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

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

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

1.1 Что такое Spring?

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

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

По своей сути Spring предлагает контейнер, часто называемый контекстом приложения Spring (Spring application context), который создает компоненты приложения и управляет ими. Эти компоненты, или beans, тесно связанные друг с другом внутри Spring application context, как кирпичи, ступени, трубопровод, проводка все вместе составляя дом.


Способ соединения bean-ов вместе основан на шаблоне, известном как dependency injection (DI). Вместо того, чтобы компоненты создавали и поддерживали жизненный цикл других компонентов, от которых они зависят, приложение с зависимостями полагается на отдельную сущность (контейнер) для создания и обслуживания всех компонентов и внедрения их в компоненты, которые в них нуждаются. Обычно это делается с помощью аргументов конструктора или методов доступа к свойствам.


Например, предположим, что среди множества компонентов приложения есть два, к которым вы будете обращаться: служба запасов (для получения уровней запасов) и Служба продуктов (для предоставления базовой информации о продукте). Служба продуктов зависит от службы запасов, чтобы иметь возможность предоставить полный набор сведений о продуктах. На рисунке 1.1 показаны связи между этими компонентами и контекстом приложения Spring.

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


Исторически сложилось так, что контекст приложения Spring для связывания компонентов был связан с одним или несколькими XML-файлами, описывающими компоненты и их связь с другими компонентами. Например, следующий XML-код объявляет два bean-а InventoryService bean и ProductService bean, и привязку InventoryService bean в ProductService через аргументы конструктора:






Однако в последних версиях Spring чаще используется конфигурация на основе Java. Следующий класс конфигурации на основе Java эквивалентен конфигурации XML:



@Configuration


public class ServiceConfiguration {


@Bean


public InventoryService inventoryService() {


return new InventoryService();


}



@Bean


public ProductService productService() {


return new ProductService(inventoryService());


}



}


Аннотация @Configuration указывает Spring, что это класс конфигурации, который будет предоставлять bean-ы контексту приложения Spring. Методы класса конфигурации аннотируются @Bean, указывая, что объекты, которые они возвращают, должны быть добавлены как bean в контексте приложения (по умолчанию, их bean идентификаторы будут соответствовать именам методов, которые определяют их).


Конфигурация Java-based предлагает несколько преимуществ по сравнению с xml-конфигурацией, включая повышение безопасности и улучшение refactorability. Но даже при всем при этом явная настройка с помощью Java или XML необходима только в том случае, если Spring не может автоматически настроить компоненты.


Автоматическая конфигурация имеет свои корни в методах Spring известных как autowiring и component scanning. С помощью component scanning Spring может автоматически обнаруживать компоненты из classpath приложения и создавать их как bean-ы в контексте приложения Spring. С autowiring Spring автоматически объединяет компоненты с другими компонентами, от которых они зависят.


Совсем недавно, с введением Spring Boot, автоматическая настройка вышла далеко за рамки сканирования компонентов и autowiring. Spring Boot-это расширение Spring Framework, которое предлагает несколько улучшений производительности. Наиболее известным из этих усовершенствований является автоконфигурация, где Spring Boot может сделать разумные предположения о том, какие компоненты должны быть настроены и подключены друг к другу, на основе записей в classpath, переменных среды и других факторов.


Я хотел бы показать вам пример кода, демонстрирующего автоконфигурацию. Но я не могу. Видите ли, автоконфигурация очень похожа на ветер. Вы можете видеть последствия этого, но нет никакого кода, который я могу показать вам и сказать: "Смотрите! Вот пример автоконфигурации!” Такое случается, компоненты и функциональность без написания кода. Именно этот отсутствие кода имеет важное значение для автоконфигурации и делает ее такой замечательной.


Автоконфигурация Spring Boot значительно сократила объем явной конфигурации (будь то XML или Java), необходимой для построения приложения. Фактически, к тому времени, когда вы закончите пример в этой главе, у вас будет работающее приложение Spring, которое имеет только одну строку кода конфигурации Spring!


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


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

1.2 Инициализация приложения Spring

Благодаря ходу этой книги вы создадите Taco Cloud, онлайн-приложение для заказа самой замечательной еды, созданной человеком-такосом. Конечно, для достижения этой цели вы будете использовать Spring, Spring Boot и множество связанных библиотек и платформ.


Вы найдете несколько вариантов инициализации приложения Spring. Хотя я мог бы провести вас через шаги ручного создания структуры каталогов проекта и определения спецификации сборки, это потерянное время—время, которое лучше потратить на написание кода приложения. Таким образом вы собираетесь опереться на Spring Initializr для начальной загрузки приложения.


Spring Initializr-это веб-приложение на основе браузера и REST API, которое может создавать структуру проекта skeleton Spring, которую вы можете реализовать с любой функциональностью, которую вы хотите. Несколько способов использования Spring Initializr:

Из веб-приложения на http://start.spring.io

Из командной строки с помощью команды curl

Из командной строки с помощью интерфейса командной строки Spring Boot

При создании нового проекта с Spring Tool Suite

При создании нового проекта с IntelliJ IDEA

При создании нового проекта в NetBeans

Вместо того, чтобы тратить несколько страниц этой главы, рассказывая о каждом из этих вариантов, я собрал эти детали в приложении. В этой главе и на протяжении всей этой книги я покажу вам, как создать новый проект, используя мой любимый вариант: поддержка Spring Initializr в Spring Tool Suite.


Как следует из названия, Spring Tool Suite-это фантастическая среда разработки Spring. Но он также предлагает удобную функцию Spring Boot Dashboard, которая (по крайней мере, на момент написания этой статьи) недоступна ни в одном из других вариантов IDE.


Если вы не являетесь пользователем Spring Tool Suite, это нормально; мы все еще можем быть друзьями. Перейдите к приложению и замените наиболее подходящий вариант инициализации инструкциями в следующих разделах. Но знайте, что в этой книге я могу время от времени ссылаться на функции, специфичные для Spring Tool Suite, такие как Spring Boot Dashboard. Если вы не используете Spring Tool Suite, вам нужно будет адаптировать эти инструкции чтобы соответствовать вашей идее.

1.2.1 Инициализация Spring проекта с Spring Tool Suite

Чтобы начать работу с новым проектом Spring в Spring Tool Suite, перейдите в меню File и выберите New, а затем Spring Starter Project. На рисунке 1.2 показана структура меню.


Рисунок 1.2 запуск нового проекта с Инициализатором в Spring Tool Suite

После выбора Spring Starter Project появится диалоговое окно мастера создания проекта (рис. 1.3). На первой странице мастера запрашиваются общие сведения о проекте, такие как имя проекта, описание и другие важные сведения. Если вы знакомы с содержанием Maven pom.xml-файл, вы будете распознавать большинство полей как элементы, которые в конечном итоге в спецификации сборки Maven. Для приложения Taco Cloud заполните диалоговое окно, как показано на рисунке 1.3, и нажмите Next.


Рис. 1.3 указания общей информации о проекте для приложения Taco Cloud

Следующая страница мастера позволяет выбрать зависимости для добавления в проект (см. рисунок 1.4). Обратите внимание, что в верхней части диалогового окна можно выбрать версию Spring Boot, на которой будет основываться проект. По умолчанию используется самая последняя доступная версия. Это, как правило, хороший вариант, если вам не нужно ориентироваться на другую версию.


Что касается самих зависимостей, вы можете либо развернуть различные разделы и искать нужные зависимости вручную, либо искать их в поле поиска в верхней части списка доступных. Для приложения Taco Cloud установите зависимости, показанных на рисунке 1.4.

Рисунок 1.4 выбор первоначальных зависимостей

На этом этапе можно нажать кнопку Finish, чтобы создать проект и добавить его в рабочую область. Но если вы чувствуете себя более уверенным, нажмите кнопку Next еще раз, чтобы увидеть последнюю страницу мастера создания начального проекта, как показано на рисунке 1.5.

Рисунок 1.5 Дополнительные настройки Инициализации

По умолчанию мастер создания проекта вызывает Spring Initializr по адресу http://start.spring.io для генерации проекта. Как правило, нет необходимости переопределять это значение по умолчанию, поэтому можно нажать кнопку Finish на второй странице мастера. Но если по какой-то причине вы размещаете свой собственный клон Initializr (возможно, локальная копия на вашем собственном компьютере или настроенный клон, работающий внутри брандмауэра вашей компании), то вы можете изменить поле базовый Url, чтобы указать на ваш экземпляр Initializr, прежде чем нажать кнопку Finish.


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

1.2.2 Изучение структуры проекта Spring

После загрузки проекта в IDE откройте его, чтобы увидеть, что он содержит. На рисунке 1.6 показан открытый проект Taco Cloud в Spring Tool Suite.



Рисунок 1.6 начальная структура проекта Spring, как показано в Spring Tool Suite

Вы можете увидеть, что это типичная Maven или Gradle структура проекта, где исходники приложения находится в src/main/java, код -тестовом находится под в src/test/java, а не в JAVA-ресурсах, которые расположены в src/main/resources. В рамках этой структуры проекта, вы должны знать элементы:

mvnw и mvnw.cmd-это скрипты обертки Maven. Эти сценарии можно использовать для построения проекта, даже если на компьютере не установлен Maven.

pom.xml-это спецификация сборки Maven. Мы рассмотрим это глубже через минуту.

TacoCloudApplication.java-это основной класс Spring Boot, который запускает проект. Мы рассмотрим этот класс поближе через минуту.

-application.properties - этот файл изначально пуст, но предлагает место, где можно указать свойства конфигурации. Мы немного повозимся с этим файлом в этой главе, но я отложу подробное объяснение свойств конфигурации до главы 5.

static - эта папка, где вы можете разместить любой статический контент (изображения, таблицы стилей, JavaScript, и так далее), который вы хотите использовать в браузере. Изначально она пуста.

templates - в этой папке вы разместите файлы шаблонов, которые будут использоваться для отображения содержимого в браузере. Изначально она пуста, но вскоре вы добавите шаблон Thymeleaf.

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

По мере развития проекта Taco Cloud, вы будите наполнять эту структуру проекта -Java-кодом, изображениями, таблицами стилей, тестами и другими сопутствующими материалами, которые сделают ваш проект более полным. Но давайте копнем немного глубже в некоторые из пунктов, которые предоставил Spring Initializr.

Изучение спецификации сборки

При заполнении формы -Initializr вы указали, что ваш проект должен быть построен с помощью Maven. Поэтому Spring Initializr построил для вас pom.xml-файл уже заполненный выбранными параметрами. Следующий листинг показывает весь pom.xml - файл, предоставленный инициализатором.

Листинг 1.1 первоначальная спецификация сборки Maven

4.0.0

sia

taco-cloud

0.0.1-SNAPSHOT

jar

taco-cloud

Taco Cloud Example

org.springframework.boot

spring-boot-starter-parent

2.0.4.RELEASE

UTF-8

UTF-8

1.8

org.springframework.boot

spring-boot-starter-thymeleaf

org.springframework.boot

spring-boot-starter-web

org.springframework.boot


spring-boot-devtools

runtime

org.springframework.boot


spring-boot-starter-test

test

org.seleniumhq.selenium

selenium-java

test

org.seleniumhq.selenium


htmlunit-driver

test

org.springframework.boot


spring-boot-maven-plugin

Первый примечательный пункт в pom.xml-файле является элемент . Вы выбрали сборку приложения в виде исполняемого файла JAR, а не файла WAR. Это, вероятно, один из самых любопытных вариантов, которые вы сделаете, особенно для веб-приложения. В конце концов, традиционные веб-приложения Java упаковываются в виде WAR-файлов, оставляя JAR-файлы предпочтительной упаковкой для библиотек и экзотического настольного UI-приложения.


Выбор упаковывать JAR для облако-ориентированных приложений правильный выбор. В то время как файлы WAR идеально подходят для развертывания на традиционном сервере приложений Java, они не подходят для большинства облачных платформ. Хотя некоторые облачные платформы (например, Cloud Foundry) способны развертывать и запускать файлы WAR, все облачные платформы Java способны запускать исполняемый файл JAR. Поэтому Spring Initializr по умолчанию использует jar-упаковку, если не указано иное.


Если вы планируете развернуть приложение на традиционном сервере приложений Java, вам потребуется выбрать War packaging и включить класс веб-инициализатора. Более подробно мы рассмотрим создание файлов WAR в главе 2.


Затем обратите внимание на элемент и, более конкретно, его дочерний элемент . Это указывает, что родительским POM проекта является spring-boot-starter-parent. Помимо прочего, Родительский POM обеспечивает управление зависимостями для нескольких библиотек, обычно используемых в проектах Spring. Для библиотек, охватываемых родительским POM, указывать версию не нужно, так как она наследуется от родительской. В версии 2.0.4.RELEASE, указывается, что вы используете Spring Boot 2.0.4 и, таким образом, унаследуете управление зависимостями, как определено этой версией Spring Boot.


Хотя мы говорим о зависимостях, обратите внимание, что есть три зависимости, объявленные в элементе . Первые две должны выглядеть несколько знакомыми. Они напрямую соответствуют зависимостям Web и Thymeleaf, выбранным до нажатия кнопки Готово в мастере создания проекта Spring Tool Suite. Третья зависимость предоставляет множество полезных возможностей тестирования. Вам не нужно было устанавливать флажок, чтобы он был включен, потому что Spring Initializr предполагает (надеюсь, правильно), что вы будете писать тесты.


Можно также заметить, что все три зависимости имеют слово starter в идентификаторе артефакта. Зависимости Spring Boot starter отличаются тем, что обычно сами по себе не имеют кода библиотеки, а вместо этого временно извлекают другие библиотеки. Эти стартовые зависимости предлагают три основных преимущества:

Файл сборки будет значительно меньше и проще в управлении, так как вам не нужно будет объявлять зависимость от каждой библиотеки, которая вам может понадобиться.

Вы можете думать о своих зависимостях с точки зрения возможностей, которые они предоставляют, а не с точки зрения имен библиотек. Если вы разрабатываете веб-приложение, вы добавите зависимость web starter, а не список отдельных библиотек, которые позволяют писать веб-приложение.

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

Наконец, спецификация сборки заканчивается плагином Spring Boot. Этот плагин выполняет несколько важных функций:

Это является заданием Maven, которое позволяет запускать приложение с помощью Maven. Вы попробуете это в разделе 1.3.4.

Это гарантирует, что все библиотеки зависимостей включены в исполняемый файл JAR и доступны в пути к классам среды выполнения.

Это создает файл манифеста в файле JAR, который обозначает класс начальной загрузки (TacoCloudApplication, в вашем случае) как основной класс для исполняемого JAR.

Говоря о классе начальной загрузки (bootstrap), давайте откроем его и посмотрим поближе.

Начальная загрузка приложения

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

Листинг 1.2 Taco Cloud bootstrap class

package tacos;

import org.springframework.boot.SpringApplication;


import org.springframework.boot.autoconfigure.SpringBootApplication;



@SpringBootApplication


public class TacoCloudApplication {


public static void main(String[] args) {


SpringApplication.run(TacoCloudApplication.class, args);


}


}

Одна из самых мощных строк кода также является одной из самых коротких. Аннотация @SpringBootApplication ясно указывает, что это приложение Spring Boot. Но в @SpringBootApplication есть больше, чем кажется на первый взгляд.

@SpringBootApplication - это составное приложение, которое объединяет три других аннотации:

- @SpringBootConfiguration - обозначает, что этот класс является классом конфигурации. Несмотря на то, что в классе еще не так много конфигурации, при необходимости можно добавить Java-based Spring Framework конфигурацию. Эта аннотация является, фактически, специализированной формой аннотации @Configuration.

- @EnableAutoConfiguration - включает автоматическое Spring Boot конфигурирование. Мы поговорим об автоконфигурации позже. На данный момент знайте, что эта аннотация говорит Spring Boot автоматически настраивать любые компоненты, которые, по его мнению, вам понадобятся.

- @ComponentScan - включает сканирование компонентов. Это позволяет объявлять другие классы с аннотациями, такими как @Component, @Controller, @Service и другими, чтобы Spring автоматически обнаруживала их и регистрировала как компоненты в контексте приложения Spring.

Другой важной частью TacoCloudApplication является метод main(). Это метод, который будет выполняться при выполнении файла JAR. По большей части, этот метод шаблонный код; каждое приложение Spring Boot, которое вы пишете, будет иметь метод, аналогичный или идентичный этому (за исключением различий в именах классов).


Метод main () вызывает статический метод run () класса SpringApplication, который выполняет фактическую загрузку приложения, создавая контекст приложения Spring. Два параметра, переданные методу run (), являются конфигурационным классом и аргументы командной строки. Хотя это не обязательно, что конфигурационный класс, передаваемый в run(), будет таким же, как класс начальной загрузки, это наиболее удобно и стандартный выбор.

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

ТЕСТИРОВАНИЕ ПРИЛОЖЕНИЯ

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

Листинг 1.3 Базовый тест приложения

package tacos;


import org.junit.Test;


import org.junit.runner.RunWith;


import org.springframework.boot.test.context.SpringBootTest;


import org.springframework.test.context.junit4.SpringRunner;



@RunWith(SpringRunner.class) /*Использование Spring runner*/


@SpringBootTest /* String Boot тест*/


public class TacoCloudApplicationTests {


@Test /*Метод теста*/


public void contextLoads() {


}


}

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

Также обратите внимание на класс с аннотацией @RunWith (SpringRunner.класс.) @RunWith является аннотацией JUnit, обеспечивая тестовый runner, помогающий JUnit в выполнении теста. Думайте об этом как о применении плагина к JUnit для обеспечения пользовательского поведения тестирования. В этом случае JUnit получает SpringRunner, тестовый модуль, предоставляемый Spring, который обеспечивает создание контекста приложения Spring, с которым будет выполняться тест.

ТЕСТ-RUNNER ПОД ДРУГИМ ИМЕНЕМ…

Если вы уже знакомы с написанием тестов Spring или, возможно, видели какие-то реальные классы тестов Spring, вы, возможно, видели тестовый runner с именем SpringJUnit4ClassRunner. SpringRunner является псевдонимом SpringJUnit4ClassRunner и был введен в Spring 4.3, чтобы удалить связь с определенной версией JUnit (например, JUnit 4). И нет никаких сомнений в том, что псевдоним легче читать и печатать.

@SpringBootTest говорит JUnit что, запуск тестов должен осуществляться с использованием Spring Boot. Сейчас достаточно считать, что этот тест класса эквивалентно вызову SpringApplication.run() в методе main(). В течение этой книги вы увидите @SpringBootTest несколько раз, и мы раскроем часть его силы.

Наконец, есть сам метод тестирования. Хотя @RunWith(SpringRunner.class) и @SpringBootTest поручено загрузить контекст приложения Spring для теста, они не будут иметь ничего общего, если нет никаких методов тестирования. Даже без каких-либо объявлений или кода, этот пустой метод тестирования предложит двум аннотациям выполнить свою работу и загрузить контекст приложения Spring. Если при этом возникают какие-либо проблемы, тест не выполняется.

На этом мы завершили обзор кода, предоставленного Spring Initializr. Вы видели некоторые стандартные основы, которые можно использовать для разработки приложения Spring, но вы до сих пор не написали ни одной строки кода. Теперь пришло время запустить IDE, убрать пыль с клавиатуры и добавить пользовательский код в приложение Taco Cloud.

Написание приложения Spring

Так как, вы только начинаете, мы начнем с относительно небольшого изменения в приложении Taco Cloud, но такого, которое продемонстрирует большую полезность Spring. Кажется уместным, что, поскольку вы только начинаете, первая функция, которую вы добавите в приложение Taco Cloud, - это домашняя страница. Чтобы добавить главную страницу, вы создадите два artifact:

контроллер-класс, который обрабатывает запросы для главной страницы

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

И помня как важно тестирование, вы также написать простой тестовый класс для проверки домашней страницы. Но сначала о главном ... давайте напишем контроллер.

1.3.1 Обработка веб-запросов

Spring поставляется с мощным веб-фреймворком, известным как Spring MVC. В центре Spring MVC находится концепция контроллера, класса, который обрабатывает запросы и отвечает какой-либо информацией. В случае обращения веб-приложения, контроллер отвечает, при необходимости заполняя данные модели и передавая запрос представлению для создания HTML-кода, возвращаемого браузеру. Вы узнаете много нового о Spring MVC в главе 2. Но пока вы напишете простой класс контроллера, который обрабатывает запросы корневого пути (например,/) и пересылает эти запросы в представление домашней страницы без заполнения данных модели. Ниже приведен простой класс контроллера.

Листинг 1.4 контроллер домашней страницы

package tacos;

import org.springframework.stereotype.Controller;


import org.springframework.web.bind.annotation.GetMapping;


@Controller /*Контроллер*/


public class HomeController {



@GetMapping("/") /*Обрабатывает запросы корневого пути */


public String home() {


return "home"; /*Возвращает имя представления*/


}


}

Как вы можете видеть, этот класс аннотируется @Controller. Сам по себе @Controller ничего не делает. Его основное назначение-идентифицировать этот класс как компонент для сканирования компонентов. Поскольку HomeController аннотируется @Controller, при сканирование компонентов Spring автоматически обнаруживает его и создает экземпляр HomeController в качестве компонента в контексте приложения Spring.

Фактически, несколько различных аннотаций (включая @Component, @Service и @Repository) служат цели, подобной @Controller. Вы могли бы так же эффективно аннотировать HomeController с любой из этих других аннотаций, и он все равно работал бы так же. Выбор @Controller, однако, более описателен роли этого компонента в приложении.

Метод home() так же прост, как методы контроллера. Он аннотируется @GetMapping, чтобы указать, что если HTTP GET запрос получен для корневого пути /, то этот метод должен обработать такой запрос. Все что он делает - это возвращение String значения “home”.

Это значение интерпретируется как логическое имя представления. Реализация этого представления зависит от нескольких факторов, но поскольку Thymeleaf находится в classpath, можно для этого задать шаблон с помощью Thymeleaf.

ПОЧЕМУ ИМЕННО THYMELEAF?

Вы можете быть удивлены, почему мы выбрали Thymeleaf для шаблонизатора. Почему не JSP? Почему не FreeMarker? Почему не один из нескольких других вариантов?

Проще говоря, я должен был выбрать что-то, и я люблю Thymeleaf и вообще предпочитаю его над всеми другими вариантами. И хотя JSP может показаться очевидным выбором, есть некоторые проблемы, которые необходимо преодолеть при использовании JSP с Spring Boot. Я не хотел спускаться в кроличью нору в первой главе. Держись. Мы рассмотрим другие варианты шаблонов, включая JSP, в главе 2.

Имя шаблона является производным от логическое имени представления, предварив его с префиксом /templates/ и .HTML. Результирующий путь для шаблона - /templates/home.HTML. Поэтому вам нужно будет разместить шаблон в вашем проекте в /src/main/resources/templates/home.HTML. Давайте создадим этот шаблон сейчас.

1.3.2 Задание представления

В интересах сохранения простоты домашней страницы, она не должно делать ничего, кроме как приветствовать пользователей на сайте. В следующем листинге показан базовый шаблон Thymeleaf, определяющий домашнюю страницу Taco Cloud.

Листинг 1.5 Шаблон домашней страницы Taco Cloud



xmlns:th="http://www.thymeleaf.org">



Taco Cloud




Welcome to...




Нечего обсуждать по поводу этого шаблона. Единственной заметной строкой кода является строка с тегом для отображения логотипа Taco Cloud. Он использует атрибут Thymeleaf th:src и @{...} выражение для ссылки на изображения с контекст-относительным путем. Кроме этой особенности, это не намного больше, чем страница Hello World.

Но давайте поговорим об этой картинке немного подробнее. Я оставлю это на вас, чтобы определить логотип Taco Cloud, который вам нравится. Вам нужно будет убедиться, что вы разместите его в нужном месте в проекте.

Изображения есть ссылка с контексто-относительным путем /images/TacoCloud.png. Как вы помните из нашего обзора структуры проекта, статическое содержимое, такое как изображения, хранится в папке/src/main/resources /static. Это означает, что изображение логотипа Taco Cloud также должно находиться в проекте по адресу / src/main/resources/static/images/TacoCloud.png.

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

1.3.3 Тестирование контроллера

Тестирование веб-приложений может быть сложным при утверждении содержимого HTML-страницы. К счастью, Spring поставляется с мощной тестовой поддержкой, которая упрощает тестирование веб-приложения. Для целей домашней страницы вы напишете тест, сравнимый по сложности с самой домашней страницей. Ваш тест выполнит HTTP-запрос GET для корневого пути / и ожидает успешного результата, когда имя представления является домашним, а результирующее содержимое содержит фразу " Добро пожаловать...”.

Листинг 1.6 Тест для контроллера страницы

package tacos;

import static org.hamcrest.Matchers.containsString;


import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;


import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;


import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;


import org.junit.Test; import org.junit.runner.RunWith;


import org.springframework.beans.factory.annotation.Autowired;


import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;


import org.springframework.test.context.junit4.SpringRunner;


import org.springframework.test.web.servlet.MockMvc;



@RunWith(SpringRunner.class)


@WebMvcTest(HomeController.class) /*Веб-тест для HomeController*/


public class HomeControllerTest {



@Autowired


private MockMvc mockMvc; /*Внедряет MockMvc*/

@Test


public void testHomePage() throws Exception {


mockMvc.perform(get("/")) /*Выполняет GET */


.andExpect(status().isOk()) /*-Ожидает HTTP 200*/


.andExpect(view().name("home")) /*Ожидает home*/


.andExpect(content().string( /*Welcome to...*/



containsString("Welcome to..."))); }


}

Первое, что вы могли бы заметить в этом тесте, это то, что написанное немного отличается от класса TacoCloudApplicationTests в части аннотаций, примененных к нему. Вместо разметки @SpringBootTest, HomeControllerTest аннотируется @WebMvcTest. Это специальная тестовая аннотация, предоставляемая Spring Boot, которая организует выполнение теста в контексте приложения Spring MVC. Более конкретно, в этом случае, он организует для HomeController регистрацию с использованием Spring MVC, так что вы можете отправлять запросы из него.

@WebMvcTest также настраивает поддержку Spring для тестирования Spring MVC.

Метод testHomePage() определяет тест, который вы хотите выполнить на главной странице. Он начинается с объекта MockMvc для выполнения запроса HTTP GET для / (корневой путь). К этому запросу устанавливаются следующие ожидания:

ответ должен иметь состояние HTTP 200 (OK).

view должен иметь логическое название home.

Отображаемое представление должно содержать текст "Welcome to...”

Если после того, как объект MockMvc выполняет запрос, любое из этих ожиданий не выполняется, то тест завершается неудачей. Но ваш контроллер и шаблон представления написаны, чтобы удовлетворить эти ожидания, поэтому тест должен пройти удачно — или, по крайней мере, с некоторым оттенком зеленого, указывающим на прохождение теста.

Контроллер был написан, шаблон представления создан, и у вас есть тест. Похоже, что вы успешно реализовали домашнюю страницу. Но даже несмотря на то, что тест проходит, есть что-то более важное, визуальный результат в браузере. В конце концов, именно это клиенты Taco Cloud увидят. Давайте создадим приложение и запустим его.

1.3.4 Создание и запуск приложения

Так же, как существует несколько способов инициализации приложения Spring, существует несколько способов его запуска.

Так как для инициализации и работы над проектом вы выбрали Spring Tool Suite, у вас есть удобная функция Spring Boot Dashboard, которая поможет запустить приложение в среде IDE. Панель мониторинга Spring Boot отображается в виде вкладки, обычно в левом нижнем углу окна IDE. На рисунке 1.7 показан аннотированный снимок экрана панели управления Spring Boot Dashboard.

Я не хочу тратить много времени на все, что делает Spring Boot Dashboard, хотя рисунок 1.7 охватывает некоторые из самых полезных деталей. Сейчас важно знать, как использовать его для запуска приложения Taco Cloud. Убедитесь, что приложение taco-cloud выделено в списке проектов (это единственное приложение, показанное на рисунке 1.7), а затем нажмите кнопку start (самая левая кнопка с зеленым треугольником и красным квадратом). Приложение должно запуститься.

Рисунок 1.7 основные моменты панели Spring Boot Dashboard



Запуск/перезапуск выбранного проекта

Запуск/перезапуск выбранного проекта в режиме отладки

Остановить выбранный проект

Открывает веб-браузер с запущенным приложении

Открывает консоль запущенного приложения

Список проектов Spring Boot

Указывает, что в проекте включены средства разработки Spring Boot

Указывает, что запущенное приложение прослушивает порт 8080

При запуске приложения вы увидите, как в консоли пролетает Spring ASCII art, а затем некоторые записи журнала, описывающие шаги при запуске приложения. Прежде чем журнал остановится, вы увидите запись в журнале о том, что Tomcat запущен на портах: 8080 (http), что означает, что вы готовы указать свой веб-браузер на главной странице, чтобы увидеть плоды своего труда.

Подожди минутку. Tomcat запустился? Вы развернули приложение в Tomcat?

Приложения Spring Boot, как правило, приносят с собой все, что им нужно, и их не нужно развертывать на каком-либо сервере приложений. Вы никогда не развертывали приложение в Tomcat ... Tomcat является частью вашего приложения! (Я опишу детали того, как Tomcat стал частью вашего приложения в разделе 1.3.6.)

Теперь, когда приложение запущено, укажите в веб-браузере http://localhost:8080 (или нажмите кнопку Глобус на панели Spring Boot Dashboard), и вы увидите что-то вроде рисунка 1.8. Ваши результаты могут отличаться, если вы разработали свой собственный логотип. Но это не должно сильно отличаться от того, что вы видите на рисунке 1.8.

Рисунок 1.8 Начальная страница Taco Cloud



Вроде особо не на что смотреть. Но это не совсем книга по графическому дизайну. Скромной главной страницы более чем достаточно.


Одна вещь, которую я не рассмотрел до сих пор это DevTools. Вы выбрали его в качестве зависимости при инициализации проекта. Это отражено в зависимостях в pom.xml файле. И Spring Boot Dashboard даже показывает, что в проект включен DevTools. Но что такое DevTools и что он делает для вас? Давайте рассмотрим несколько наиболее полезных функций DevTools.

1.3.5 Знакомство с Spring Boot DevTools

Как следует из названия, DevTools предоставляет разработчикам Spring несколько удобных инструментов для разработки. Среди них

автоматическая перезагрузки приложения если код меняется

автоматическое обновление приложения в браузера когда обновляются ресурсы предназначенные для браузерного отображения (такие как шаблоны, Скрипты, таблицы стилей и т. д.)

автоматическое отключение кэша шаблона

сборка в Н2 консоли, если используется Н2 БД

Важно понимать, что DevTools это не плагин IDE, т.е. не требуют использования конкретных IDE. Он одинаково хорошо работает в Spring Tool Suite, IntelliJ IDEA и NetBeans. Кроме того, поскольку он предназначен только для целей разработки, он достаточно умен, чтобы отключить себя при развертывании в рабочей среде. (Мы обсудим, как это делается, когда вы приступите к развертыванию приложения в главе 19.) Теперь давайте сосредоточимся на наиболее полезных функциях Spring Boot DevTools, начиная с автоматического перезапуска приложения.

Автоматический перезапуск приложения

С помощью DevTools в рамках вашего проекта вы сможете вносить изменения в код Java и файлы свойств в проекте и видеть, как эти изменения применяются почти сразу.

DevTools отслеживает изменения, и когда он видит, что что-то изменилось, он автоматически перезапустить приложение.

Точнее, когда DevTools запущен, приложение загружается в два отдельных загрузчика класса в виртуальной машине Java (JVM). Один загрузчик класса загружается с вашим кодом Java, файлами свойств и почти всем, что находится в src/main/path проекта. Это элементы, которые могут часто изменяться. Другой класс loader загружается библиотеками зависимостей, которые вряд ли будут меняться так часто.

При обнаружении изменения DevTools перезагружает только загрузчик классов, содержащий код проекта, и перезапускает контекст приложения Spring, но оставляет другой загрузчик классов и JVM нетронутыми. Хотя эта стратегия и тонкая, она позволяет немного сократить время, необходимое для запуска приложения.

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

АВТОМАТИЧЕСКОЕ ОБНОВЛЕНИЕ БРАУЗЕРА И ОТКЛЮЧЕНИЕ КЭША ШАБЛОНОВ

По умолчанию параметры шаблона, такие как Thymeleaf и FreeMarker, настроены для кэширования результатов синтаксического анализа шаблона, так чтобы шаблоны не нужно было анализировать с каждым запросом, который они обслуживают. Это хорошо для продакшена.

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

DevTools устраняет эту проблему, автоматически отключая кэширование всех шаблонов. Внесите столько изменений, сколько вы хотите, в свои шаблоны и проверьте их сразу обновив браузер.

Но если вы похожи на меня, вы даже не хотите обременяться усилиями нажатия кнопки обновления браузера. Было бы намного лучше, если бы вы могли внести изменения и сразу увидеть результаты в браузере. К счастью, в DevTools есть что-то особенное для тех из нас, кому лень нажимать кнопку обновления.

Когда DevTools запущен, он автоматически включает LiveReload (http://livereload.com/) сервер вместе с вашим приложением. Сам по себе сервер LiveReload не очень полезен. Но в сочетании с соответствующим плагином браузера LiveReload он заставляет ваш браузер автоматически обновляться при внесении изменений в шаблоны, изображения, таблицы стилей, JavaScript и т. д.—фактически, почти все, что в конечном итоге отправляется в ваш браузер.

LiveReload имеет плагины для браузеров Google Chrome, Safari и Firefox. ((Сорри, Internet Explorer и Edge фанаты)) Посетите http://livereload.com/extensions/ чтобы найти информацию о том, как установить LiveReload для Вашего браузера.

Сборка в H2 консоле

Хотя ваш проект еще не использует базу данных, это изменится в главе 3. Если вы решите использовать базу данных H2 для разработки, DevTools также автоматически активирует консоль H2, доступ к которой можно получить из веб-браузера. Вам нужно только указать в веб-браузере http://localhost:8080/h2-console чтобы получить представление о данных, с которыми работает приложение.

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

1.3.6 Давайте рассмотрим

Вспомните, как вы дошли до этого момента. Короче говоря, это шаги, которые Вы предприняли для создания Spring-based Taco Cloud приложения:

Вы создали первоначальную структуру проекта с помощью Spring Initializr.

Вы написали класс контроллера для обработки запроса домашней страницы.

Вы определили шаблон представления для отображения главной страницы.

Вы написали простой тестовый класс, чтобы проверить что все работает.

Кажется довольно просто, не так ли? За исключением первого шага по начальной загрузке проекта, каждое действие, которое Вы предприняли, было сосредоточено на достижении цели создания домашней страницы

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

Это важное преимущество разработки с Spring. Можно сосредоточиться на коде, удовлетворяющем требованиям приложения, а не на удовлетворении требованиям framework-а. Хотя вам, несомненно, придется время от времени писать специфичный для framework-а код, обычно это будет лишь небольшая часть вашей работы. Как я уже говорил, Spring (с Spring Boot)можно считать frameworkless framework.

Как это вообще работает? Что Spring делают под капотом, чтобы убедиться, что ваши требования выполняются? Чтобы понять, что делает Spring, давайте начнем с рассмотрения спецификации сборки.

В файле pom.xml, вы объявили зависимость от Web и Thymeleaf starter-ов. Эти две зависимости транзитивно принесли несколько других зависимостей, в том числе

Spring’s MVC framework

Встроенный Tomcat

Thymeleaf и макет Thymeleaf диалекта

Также добавлены Spring Boot-овские конфигурируемые при запуске библиотеки. При запуске приложения Spring Boot автоконфигурирование обнаруживает эти библиотеки и автоматически:

Настраивает bean-ы в контексте приложения Spring для включения Spring MVC

Настраивает встроенный сервер Tomcat в контексте приложения Spring

Настраивает Thymeleaf view resolver для рендеринга Spring MVC представления с Thymeleaf шаблонами

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

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

1.4 Съемка Spring ландшафта

Чтобы получить представление о Spring ландшафте, посмотрите на огромный список флажков в полной версии веб-формы Spring Initializr. В нем перечислены более 100 вариантов зависимостей, поэтому я не буду пытаться перечислить их все здесь или предоставить скриншот. Но я призываю вас взглянуть самим. В то же время, я упомяну некоторые основные моменты.

1.4.1 Core Spring Framework

Как и следовало ожидать, ядро Spring Framework является основой всего остального в Spring вселенной. Оно предоставляет базовый контейнер и инфраструктуру внедрения зависимостей. Но оно также предоставляет несколько других важных функций.

Среди них Spring MVC, веб-фреймворк Spring. Вы уже видели, как использовать Spring MVC для написания класса контроллера для обработки веб-запросов. Однако вы еще не видели, что Spring MVC также можно использовать для создания REST API, которые возвращают ответ не в виде HTML. Мы собираемся углубиться в Spring MVC в главе 2, а затем еще раз взглянуть на то, как использовать его для создания REST API в главе 6.

Ядро Spring Framework также предлагает некоторую элементарную поддержку сохраняемости данных, в частности на основе шаблонов поддержки JDBC. Вы увидите, как использовать JdbcTemplate в главе 3.

В последней версии Spring (5.0.8) была добавлена поддержка программирования в реактивном стиле, включая новую реактивную веб-платформу Spring WebFlux, которая в значительной степени заимствуется из Spring MVC. Вы посмотрите на модель реактивного программирования Spring в части 3 и Spring WebFlux отдельно в главе 10.

1.4.2 Spring Boot

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

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

Гибкая спецификация свойств среды.

Дополнительное тестирование поддержки поверх тестирования core framework.

Более того, Spring Boot предлагает альтернативную модель программирования, основанную на скриптах Groovy, которая называется Spring Boot CLI (интерфейс командной строки). С помощью интерфейса командной строки Spring Boot можно создавать целые приложения в виде набора сценариев Groovy и запускать их из командной строки. Мы не будем тратить много времени на Spring Boot CLI, но мы коснемся его, когда он будет нам необходим.

Spring Boot стал неотъемлемой частью разработки на Spring; я не могу представить разработку Spring-приложения без него. Следовательно, эта книга имеет структуру, ориентированную на Spring Boot, и вы можете поймать меня, на использование слова Spring, когда я имею в виду что-то, что делает Spring Boot.

1.4.3 Spring Data

Несмотря на то, что базовая платформа Spring поставляется с поддержкой сохранения базовых данных, Spring Data предоставляет нечто удивительное: возможность определять репозитории данных вашего приложения как простые интерфейсы Java, используя соглашение об именовании при определении методов управления хранением и извлечением данных. Более того, Spring Data способна работать с несколькими различными типами баз данных, включая реляционные (JPA), document (Mongo), graph (Neo4j) и другие. Вы будете использовать Spring Data для создания репозиториев для приложения Taco Cloud в главе 3.

1.4.4 Spring Security

Безопасность приложений всегда была важной темой, и с каждым днем она становится все более важной. К счастью, Spring имеет надежные границы безопасности в Spring Security. Spring Security удовлетворяет широкий спектр потребностей безопасности приложений, включая проверку подлинности, авторизацию и безопасность API. Хотя объем Spring Security слишком велик, чтобы быть должным образом рассмотрены в этой книге, мы коснемся некоторых из наиболее распространенных случаев использования в главах 4 и 12.

1.4.5 Spring Integration и Spring Batch

В какой-то момент большинству приложений потребуется интеграция с другими приложениями или даже с другими компонентами того же приложения. Для удовлетворения этих потребностей было разработано несколько моделей интеграции прикладных программ. Spring Integration и Spring Batch обеспечивают реализацию этих шаблонов для приложений на основе Spring.

Spring Integration относиться к интеграции в реальном времени, где данные обрабатываются по мере их доступности. В отличие от этого, Spring Batch относиться к пакетной интеграции, где данные разрешено собирать в течение времени, пока какой-либо триггер (возможно, триггер времени) не сигнализирует, что пришло время отправки пакета данных на обработку. Вы изучите как Spring Batch, так и Spring Integration в главе 9.

1.4.6 Spring Cloud

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

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

Spring Cloud содержит множество полезных компонентов, и было бы невозможно охватить все это в этой книге. Мы рассмотрим некоторые из наиболее распространенных компонентов Spring Cloud в главах 13, 14 и 15. Для более полного обсуждения Spring Cloud я предлагаю взглянуть на Spring Microservices in Action от John Carnell (Manning, 2017, www.manning .com/books/spring-microservices-in-action).

Итог

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

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

Spring приложения могут быть инициализированы с помощью Spring Initializr, который web-ориентирован и поддерживается изначально в большинстве Java-вских средах разработки.

Компоненты, обычно называемые bean-ами, в контексте приложения Spring могут быть объявлены явным образом с помощью Java или XML, обнаружены при сканировании компонентов или автоматически настроены с автоматической настройкой Spring Boot.

Spring in Action Covers Spring 5.0 перевод на русский. Глава 2

2.Разработка web-приложений

В этой главе рассматривается



представление данных модели в браузере

обработка и проверка форм ввода

выбор библиотеки шаблонов представлений

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

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

В главе 1 Вы создали свой первый контроллер Spring MVC для отображения домашней страницы приложения. Но Spring MVC может делать гораздо больше, чем просто отображать статический контент. В этой главе вы разработаете первый крупный компонент функциональности в вашем Taco Cloud приложении - возможность создавать собственные такос. При этом вы углубитесь в Spring MVC и увидите, как отображать данные модели и обрабатывать данные форм ввода.

2.1 Отображение информации

По сути, Taco Cloud-это место, где вы можете заказать тако онлайн. Но более того, в Taco Cloud хочется предоставить возможность своим клиентам выразить свою творческую сторону и разработать собственные тако из богатой палитры ингредиентов.

Поэтому веб-приложению Taco Cloud требуется страница, на которой отображается выбор ингредиентов для создателей тако. Выбор ингредиентов может измениться в любое время, поэтому они не должны быть жестко закодированы в HTML-страницу. Скорее, список доступных ингредиентов должен быть извлечен из базы данных и передан на страницу, которая будет отображаться для клиента.

В веб-приложении Spring задача контроллера заключается в получении и обработке данных. Также должна производить обработка данных в представление, чтобы отобразить эти данные в HTML, которые будут отображаться в браузере. Мы собираемся создать следующие компоненты для страницы создания тако:

доменный класс, который определяет свойства тако ингредиента

Spring MVC класс контроллера, который вытаскивает информацию об ингредиенте и передает ее на отображение

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

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

Рисунок 2.1 типичный поток запросов Spring MVC

Поскольку в этой главе основное внимание уделяется веб-платформе Spring, мы перенесем весь материал базы данных на Главу 3. На данный момент контроллер будет нести полную ответственность за предоставление ингредиентов для представления. В главе 3 Вы переработаете контроллер для совместной работы с репозиторием, который извлекает данные ингредиентов из базы данных.

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

2.1.1 Создание домена

Домен приложения-это тематика, которую оно затрагивает—идеи и концепции, которые влияют на понимание приложения (Для более глубокого обсуждения областей применения я предлагаю дизайн, управляемый доменами Эрика Эванса (Addison-Wesley Professional, 2003).). В приложении Taco Cloud домен включает такие объекты, как описание тако, ингредиенты, из которых состоят эти описания, клиенты и заказы тако, размещенные клиентами. Для начала мы сосредоточимся на ингредиентах тако.

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

Листинг 2.1 Определение ингредиента тако

package tacos;

import lombok.Data;


import lombok.RequiredArgsConstructor;



@Data


@RequiredArgsConstructor


public class Ingredient {


private final String id;


private final String name;


private final Type type;



public static enum Type {


WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE


}


}

Как вы можете видеть, это заурядный класс домена Java, определяющий три свойства, необходимые для описания ингредиента. Возможно, самое необычное в классе Ingredient, как он определен в листинге 2.1, заключается в том, что в нем отсутствует обычный набор методов getter и setter, не говоря уже о таких полезных методах, как equals (), hashCode (), toString () и других.

Вы не видите их в списке частично, чтобы сэкономить место, но и потому, что вы используете удивительную библиотеку под названием Lombok для автоматического создания этих методов во время выполнения. Фактически, аннотация @Data на уровне класса предоставляется Lombok и указывает Lombok генерировать все эти отсутствующие методы, а также конструктор, который принимает все конечные свойства в качестве аргументов. Путем использование Lombok, вы можете держать код для Ingredient коротким и понятным.

Lombok-это не Spring-овая библиотека, но она настолько невероятно полезна, что мне уже трудно без нее. И она спасает, когда мне нужно чтобы примеры кода в книге были короткими и понятными.

Чтобы использовать Lombok, вам нужно добавить его в качестве зависимости в свой проект. Если вы используете Spring Tool Suite, это простой вопрос, щелкнув правой кнопкой мыши на pom.xml-файл и выбор редактировать стартеры из опции контекстного меню Spring. Появится тот же выбор зависимостей, что и в главе 1 (на рис. 1.4), что дает вам возможность добавить или изменить выбранные зависимости. Найдите выбор Lombok, убедитесь, что он отмечен, и нажмите OK; Spring Tool Suite автоматически добавит его в вашу спецификацию сборки.

Кроме того, можно вручную добавить его со следующей записью в pom.xml:


org.projectlombok


lombok


true


Эта зависимость предоставит вам аннотации Lombok (например, @Data) во время разработки и с автоматической генерацией методов во время выполнения. Но вам также потребуется добавить Lombok в качестве расширения в IDE, иначе IDE будет жаловаться на ошибки, связанные с отсутствующими методами и конечными свойствами, которые не задаются. Посетите https://projectlombok.org/ чтобы узнать, как установить Lombok в выбранной среде IDE.

Я думаю, что вы найдете Ломбок очень полезным, но знайте, что он необязателен. Он не нужен для разработки приложений Spring, поэтому если вы не хотите его использовать, не стесняйтесь писать эти недостающие методы вручную. Дерзайте... Я подожду.

2.1.2 Создание класса контроллера

Контроллеры являются основными компонентами в рамках Spring MVC. Их основная задача-обрабатывать HTTP-запросы и либо передать запрос в представление для отображения HTML (отображаемого браузером), либо записать данные непосредственно в тело ответа (RESTful). В этой главе мы сосредоточимся на типах контроллеров, использующих представления для создания содержимого для веб-браузеров. Когда мы перейдем к главе 6, мы рассмотрим написание контроллеров, которые обрабатывают запросы в REST API.

Для Taco Cloud, вам нужен простой контроллер который делает следующее:

Обрабатывать HTTP-запросы GET, для пути запроса /design

Составьте список ингредиентов

Обработать запрос и данные по ингредиентам на основе шаблона для отображения как HTML и отправить в веб-браузер.

Следующий класс DesignTacoController отвечает этим требованиям

Листинг 2.2 Начальный класс Spring контроллера

package tacos.web;

import java.util.Arrays;


import java.util.List;


import java.util.stream.Collectors;


import javax.validation.Valid;


import org.springframework.stereotype.Controller;


import org.springframework.ui.Model;


import org.springframework.validation.Errors;


import org.springframework.web.bind.annotation.GetMapping;


import org.springframework.web.bind.annotation.PostMapping;


import org.springframework.web.bind.annotation.RequestMapping;


import lombok.extern.slf4j.Slf4j;


import tacos.Taco;


import tacos.Ingredient;


import tacos.Ingredient.Type;



@Slf4j


@Controller


@RequestMapping("/design")


public class DesignTacoController {



@GetMapping


public String showDesignForm(Model model) {


List ingredients = Arrays.asList(


new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),


new Ingredient("COTO", "Corn Tortilla", Type.WRAP),


new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),


new Ingredient("CARN", "Carnitas", Type.PROTEIN),


new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),


new Ingredient("LETC", "Lettuce", Type.VEGGIES),


new Ingredient("CHED", "Cheddar", Type.CHEESE),


new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),


new Ingredient("SLSA", "Salsa", Type.SAUCE),


new Ingredient("SRCR", "Sour Cream", Type.SAUCE)


);


Type[] types = Ingredient.Type.values();


for (Type type : types) {


model.addAttribute(type.toString().toLowerCase(),


filterByType(ingredients, type));


}


model.addAttribute("design", new Taco());


return "design";


}

}

Первое, что следует отметить об DesignTacoController, - это набор аннотаций, применяемых на уровне класса. Первый, @Slf4j, является аннотацией, предоставляемой Lombok, которая во время выполнения автоматически генерирует SLF4J (Simple Logging Facade for Java, https://www.slf4j.org/) Logger в классе. Эта скромная аннотация имеет тот же эффект, что и при явном добавлении следующих строк в класс:

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DesignTacoController.class);

Вы будете использовать этот Logger немного позже.

Следующая аннотация, примененная к DesignTacoController это - @Controller. Эта аннотация служит, чтобы идентифицировать этот класс как контроллер и пометить его как кандидата на компонентное сканирование, так, чтобы Spring обнаружил его и автоматически создал экземпляр DesignTacoController как bean в контексте приложения Spring.

DesignTacoController аннотирован @RequestMapping. @RequestMapping аннотация, используемая на уровне класса, определяет тип запросов, обрабатываемых этим контроллером. В этом случае он указывает, что контроллер DesignTacoController будет обрабатывать запросы, путь к которым начинается с /design.

ОБРАБОТКА ЗАПРОСА GET

@GetMapping - относительно новая аннотация, появившаяся в Spring 4.3. До Spring 4.3 вы, возможно, использовали аннотацию @RequestMapping уровня метода вместо:

@RequestMapping(method=RequestMethod.GET)

Очевидно, что @GetMapping более сжатый и конкретный метод HTTP. @GetMapping является лишь одним членом семьи request-mapping аннотации. В таблице 2.1 перечислены все аннотации request-mapping, доступные в Spring MVC.

Таблица 2.1 Spring MVC request-mapping аннотации


Аннотации

Описание


@RequestMapping

обработка запросов общего назначения


@GetMapping

обрабатывает запросы HTTP GET


@PostMapping

обрабатывает запросы HTTP POST


@PutMapping

обрабатывает запросы HTTP PUT


@DeleteMapping

обрабатывает HTTP-запросы на удаление


@Pathmapping

обрабатывае HTTP-запросов на патч


Делайте правильные вещи легкими

Всегда рекомендуется быть как можно более конкретным при объявлении сопоставлений запросов в методах контроллера. По крайней мере, это означает объявление как пути (или наследование пути от класса уровня @RequestMapping), так и метода HTTP, который он будет обрабатывать.

Длинный @RequestMapping((method=RequestMethod.GET) сделал заманчивым взять ленивый выход и оставить атрибут метода. Благодаря новым аннотациям отображения Spring 4.3, правильная вещь также является легкой - с меньшим количеством ввода.

Новые аннотации request-mapping имеют те же атрибуты, что и @RequestMapping, поэтому их можно использовать везде, где пришлось бы использовать @RequestMapping.

Как правило, я предпочитаю использовать только @RequestMapping на уровне класса, чтобы указать базовый путь. Я использую более конкретные @GetMapping, @PostMapping и т. д. Для каждого из методов обработчика.

Теперь, когда вы знаете, что метод showDesignForm() будет обрабатывать запрос, давайте посмотрим на тело метода, чтобы увидеть, как он устроен. Основное - это то что метод создает список объектов Ingredient. Список пока жестко закодирован. Когда мы к этому вернемся в главе 3, вы получите список доступных ингредиентов тако из базы данных

Как только список ингредиентов готов, следующие несколько строк showDesignForm() фильтруют список по типу ингредиентов. Затем список типов ингредиентов добавляется в качестве атрибута к объекту Model, который передается в showDesignForm()). Model - объект, который передает данные между контроллером и любым представлением, отвечающим за визуализацию этих данных. В конечном счете данные, помещенные в атрибуты Model, копируются в атрибуты ответа сервлета, где их можно найти в представлении. Метод showDesignForm() завершается возвратом "design", логическим именем представления, которое будет использоваться для отображения модели в браузере.

Ваш DesignTacoController действительно начинает обретать форму. Если запустить приложение сейчас и указать в браузере путь /design, будет задействована функция showDesignForm() DesignTacoController, извлекающая данные из репозитория и помещающая их в модель перед передачей запроса в представление. Но поскольку вы еще не определили представление, запрос будет не корректен, что приведет к ошибке HTTP 404 (Not Found). Чтобы исправить это, давайте переключим наше внимание на представление, где данные будут представлены в виде HTML, которые будут отображаться в веб-браузере пользователя.

2.1.3 Проектирование представления

После того, как работы над контроллером закончены, пришло время для просмотра. Spring предлагает несколько отличных опций для определения представлений, включая JavaServer Pages (JSP), Thymeleaf, FreeMarker, Mustache и шаблоны на основе Groovy. На данный момент мы будем использовать Thymeleaf, выбор, который мы сделали в главе 1 при запуске проекта. Мы рассмотрим несколько других вариантов в разделе 2.5.

Чтобы использовать Thymeleaf, необходимо добавить еще одну зависимость в сборку проекта. Следующая запись добавит Thymeleaf в Spring Boot, чтобы сделать Thymeleaf доступным для рендеринга представления, которое вы собираетесь создать:


org.springframework.boot


spring-boot-starter-thymeleaf


Во время выполнения Spring Boot autoconfiguration увидит, что Thymeleaf находится в classpath и автоматически создаст bean, которые поддерживают представления Thymeleaf для Spring MVC.

Визуальные библиотеки, такие как Thymeleaf предназначены для отделения от определенного web framework. Таким образом, они не знают об абстракции модели Spring и не могут работать с данными, которые контроллер помещает в Model. Но они могут работать с атрибутами запроса сервлета. Поэтому, прежде чем Spring передаст запрос представлению, он копирует данные модели в атрибуты запроса, к которым Thymeleaf и другие визуальные шаблонизаторы представлений имеют доступ.

Шаблоны Thymeleaf - это просто HTML с некоторыми дополнительными атрибутами элементов, которые корректируют шаблон при отображении данных запроса. Например, если бы существовал атрибут запроса, ключом которого является "message", и вы хотели, чтобы он отображался в теге HTML

с помощью Thymeleaf, вы бы написали следующее в шаблоне Thymeleaf:

placeholder message

При отображении шаблона в HTML тело элемента

будет заменено значением атрибута запроса сервлета, ключом которого является"message". Атрибут th:text является атрибутом пространства имен Thymeleaf, который выполняет замену. Оператор ${} указывает ему использовать значение атрибута запроса (в данном случае" message").

Thymeleaf также предлагает другой атрибут, th: each, который перебирает коллекцию элементов, добавляя HTML код для каждого элемента в коллекции. Это пригодится, когда вы создадите отображение, чтобы перечислить ингредиенты taco из модели. Например, чтобы отобразить только список ингредиентов "wrap", можно использовать следующий фрагмент HTML:

Designate your wrap:




INGREDIENT


Здесь используется атрибут th: each тега

, чтобы повторить отрисовку
один раз для каждого элемента коллекции, найденного в атрибуте wrap запроса. На каждой итерации элемент ингредиента привязывается к переменной Thymeleaf с именем ингредиента.

Внутри элемента

есть элемент checkbox и элемент , чтобы предоставить метку для флажка. Флажок использует Thymeleaf th:value, чтобы задать для элемента значение атрибута value, найденное в свойстве ID компонента. Элемент использует th:text для замены текста-заполнителя "INGREDIENT" значением свойства name ингредиента.

При визуализации с фактическими данными модели одна итерация цикла

может выглядеть следующим образом:



Flour Tortilla


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

Листинг 2.3 Полный вид страницы дизайна тако



xmlns:th="http://www.thymeleaf.org">



Taco Cloud





Design your taco!






Designate your wrap:




INGREDIENT





Pick your protein:




INGREDIENT





Choose your cheese:




INGREDIENT





Determine your veggies:




INGREDIENT





Select your sauce:




INGREDIENT






Name your taco creation:









Как вы можете видеть, вы повторяете фрагмент

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

Также стоит отметить, что полный шаблон включает в себя изображение логотипа Taco Cloud и ссылку на таблицу стилей(Содержание таблицы стилей не имеет отношения к нашему обсуждению; она содержит только стиль, чтобы представить ингредиенты в двух столбцах вместо одного длинного списка ингредиентов) в обоих случаях оператор @{} используется для создания относительного к контексту пути к статическим артефактам, на которые они ссылаются. Как вы узнали из главы 1, статическое содержимое в приложении Spring Boot подается из каталога /static в корне пути к классам.

Теперь, когда ваш контроллер и визуальная часть полностью готовы, вы можете запустить приложение, чтобы увидеть плоды вашего труда. Существует множество способов запуска приложения Spring Boot. В главе 1 я показал вам, как запустить приложение, сначала создав его в исполняемый файл JAR, а затем запустив JAR с java-jar. Я также показал, как запустить приложение непосредственно из сборки с помощью mvn spring-boot:run.

Независимо от того, как вы запустите приложение Taco Cloud, как только оно запустится, укажите браузеру http://localhost:8080/design. Вы должны увидеть страницу, которая выглядит примерно как на рисунке 2.2.


Рисунок 2.2 Страница тако-создания

Это выглядит замечательно! Творцам тако, посетившим ваш сайт, предоставляется форма, содержащая палитру ингредиентов тако, из которых они могут создать свой шедевр. Но что происходит, когда они нажимают кнопку Submit Your Taco?

Ваш DesignTacoController еще не готов принять созданный тако. Если форма дизайна отправлена, пользователю будет представлена ошибка. (В частности, это будет ошибка HTTP 405: метод запроса "POST" не поддерживается.) Давайте исправим это, написав еще один код контроллера, который обрабатывает отправку формы.

2.2 Обработка отправки формы

Если еще раз взглянуть на тег

в представлении, можно увидеть, что его атрибут method имеет значение POST. Кроме того, не объявляет атрибут action. Это означает, что когда форма будет отправлена, браузер соберет все данные в форме и отправит ее на сервер в запросе HTTP POST к тому же пути, для которого запрос GET отобразил форму—путь /design.

Таким образом необходим метод обработчика контроллера на принимающей стороне запроса POST. Вам нужно написать новый метод обработчика в контроллере дизайна тако, который обрабатывает запрос POST для /design.

В листинге 2.2 вы использовали аннотацию @GetMapping, чтобы указать, что метод showDesignForm() должен обрабатывать запросы HTTP GET для /design. Также как @GetMapping обрабатывает GET-запросы, вы можете использовать @PostMapping для обработки POST-запросов. Для обработки созданных тако добавьте метод processDesign() как в следующем листинге для DesignTacoController.

Листинг 2.4 Обработка POST-запросов с @PostMapping

@PostMapping public String processDesign(Design design) {


// Save the taco design…


// We'll do this in chapter 3


log.info("Processing design: " + design);


return "redirect:/orders/current";


}

Применительно к методу processDesign(), @PostMapping взаимодействует с уровнем класса @RequestMapping, чтобы указать, что processDesign() должен обрабатывать POST-запросы для /design. Это именно то, что вам нужно для обработки представленных творений создателя тако.

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

Листинг 2.5 Объект домена, определяющий состав тако

package tacos;

import java.util.List;


import lombok.Data;



@Data public class Taco {


private String name;


private List ingredients;


}

Как вы можете видеть, Taco - это простой объект Java с несколькими свойствами. Как и ингредиент, класс Taco аннотируется @Data для автоматического создания необходимых методов JavaBean для вас во время выполнения.

Если вы посмотрите на форму в листинге 2.3, вы увидите несколько элементов checkbox, все с именем ingredients, и элемент ввода текста с именем name. Эти поля в форме соответствуют непосредственно ingredients и свойствам name класса Taco.

Поле Name в форме должно содержать только простое текстовое значение. Таким образом, свойство name Taco имеет тип String. У флажков "ингредиенты" также есть текстовые значения, но поскольку можно выбрать ни одного или несколько из них, свойство ingredients, к которому они привязаны, является List, которое будет включать каждый из выбранных ингредиентов.

На данный момент метод processDesign() ничего не делает с объектом Taco. На самом деле, он вообще ничего не делает. Все нормально. В главе 3 вы добавите некоторую логику сохраняемости, которая сохранит отправленное Taco в базу данных.

Как и в случае с методом showDesignForm(), processDesign() завершается возвращением String значения. И так же, как showDesignForm(), возвращаемое значение указывает на представление, которое будет показано пользователю. Но отличается то, что значение, возвращаемое из processDesign(), имеет префикс "redirect:", указывающий, что это представление перенаправления. Более конкретно, это указывает, что после того, как processDesign() завершится, браузер пользователя должен быть перенаправлен к относительному пути /order/current.

Идея заключается в том, что после создания тако, пользователь будет перенаправлен на форму заказа, из которых он может разместить заказ, чтобы его творение - тако был доставлен. Но у вас еще нет контроллера, который будет обрабатывать запрос /orders/current.

Учитывая то, что вы теперь знаете о @Controller, @RequestMapping и @GetMapping, вы можете легко создать такой контроллер. Это может выглядеть примерно так:

Листинг 2.6 Контроллер для представления формы заказа тако

package tacos.web;

import javax.validation.Valid;


import org.springframework.stereotype.Controller;


import org.springframework.ui.Model;


import org.springframework.validation.Errors;


import org.springframework.web.bind.annotation.GetMapping;


import org.springframework.web.bind.annotation.RequestMapping;


import lombok.extern.slf4j.Slf4j;


import tacos.Order;



@Slf4j @Controller


@RequestMapping("/orders")


public class OrderController {


@GetMapping("/current")


public String orderForm(Model model) {


model.addAttribute("order", new Order());


return "orderForm";


}


}

Вы используете @Slf4j аннотацию Lombok для создания свободного объекта slf4j Logger во время выполнения. Вы будете использовать этот регистратор в момент получения деталей заказа, который был создан.

@RequestMapping уровня класса указывает, что любые методы обработки запросов в этом контроллере будут обрабатывать запросы, путь к которым начинается с /orders. В сочетании с методом уровня @GetMapping, он указывает, что метод orderForm() будет обрабатывать HTTP GET запросы для /orders/current.

Что касается самого метода orderForm(), он чрезвычайно прост, возвращая только логическое имя представления orderForm. Как только у вас будет способ сохранить созданного тако в базе данных в главе 3, вы вернетесь к этому методу и измените его, чтобы заполнить модель списком объектов Taco, которые будут размещены в очереди.

Представление формы заказа предоставляется шаблоном Thymeleaf с именем orderForm.html, который показан далее.

Листинг 2.7 Вид формы заказа тако



xmlns:th="http://www.thymeleaf.org">



Taco Cloud






Order your taco creations!



Design another taco




Please correct the problems below and resubmit.




Deliver my taco masterpieces to...






















Here's how I'll pay...

















По большей части, orderForm.html отображение - это типичный HTML/Thymeleaf контент, с очень небольшим количеством заметок. Но обратите внимание, что тег

здесь отличается от тега , используемого в листинге 2.3, тем, что он также определяет действие формы. Без указанного действия форма отправит запрос HTTP POST обратно на тот же URL-адрес, что и форма. Но здесь вы указываете, что форма должна заPOSTедь в /orders (используя оператор Thymeleaf @{...} для относительного контекста).

Поэтому в класс OrderController необходимо добавить еще один метод, обрабатывающий POST-запросы для /orders. У вас не будет способа сохранять заказы до следующей главы, поэтому поставим заглушку здесь-что-то вроде той, что вы видите в следующем листинге:

Листинг 2.8 обработка отправки заказа тако

@PostMapping


public String processOrder(Order order) {


log.info("Order submitted: " + order);


return "redirect:/";


}

При вызове метода processOrder() для обработки отправленного заказа ему присваивается объект Order, свойства которого привязаны к полям отправленной формы. Order, как и Taco, является довольно простым классом,который несет информацию о заказе.

Листинг 2.9 доменный объект для заказов тако

package tacos;

import javax.validation.constraints.Digits;


import javax.validation.constraints.Pattern;


import org.hibernate.validator.constraints.CreditCardNumber;


import org.hibernate.validator.constraints.NotBlank;


import lombok.Data;



@Data


public class Order {


private String name;


private String street;


private String city;


private String state;


private String zip;


private String ccNumber;


private String ccExpiration;


private String ccCVV;


}

Теперь, когда вы разработали OrderController и представление формы заказа, вы готовы испытать его. Откройте браузер, с адресом http://localhost:8080/design, выберите некоторые ингредиенты для вашего тако,и нажмите кнопку Submit Your Taco. Вы должны увидеть форму, подобную показанной на рис. 2.3.

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

Order submitted: Order(name=Craig Walls,street1=1234 7th Street, city=Somewhere, state=Who knows?, zip=zipzap, ccNumber=Who can guess?, ccExpiration=Some day, ccCVV=See-vee-vee)

Если вы внимательно посмотрите на запись лога из моего тестового заказа, вы увидите, что хотя метод processOrder() выполнил свою работу и обработал отправку формы, он позволил пройти немного не корректной информации. Большинство полей в форме содержат данные, которые не могут быть правильными. Давайте добавим некоторую проверку, чтобы убедиться, что предоставленные данные по крайней мере похожи на требуемую информацию.


Рисунок 2.3

2.3 Проверка формы ввода

При сборке созданного тако, что делать, если пользователь не выбирает ингредиенты или не указал имя при создании? При отправке заказа, что делать, если создатель тако не заполнил необходимые поля адреса? Или что, если он ввел значение в поле кредитной карты, которое не является действительным номером кредитной карты?

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

Одним из способов выполнения проверки формы является заполнение методов processDesign() и processOrder() множеством блоков if/then, проверяя каждое поле, чтобы убедиться, что оно соответствует соответствующим правилам проверки. Но это было бы громоздко и трудно читать и отлаживать.

К счастью, Spring поддерживает API проверки компонентов Java (также известный как JSR-303; https://jcp.org/en/jsr/detail?id=303). Это упрощает объявление правил проверки в отличие от явного написания логики объявления в коде приложения. И с Spring Boot, вам не нужно делать ничего особенного, чтобы добавить библиотеки проверки в свой проект, потому что API проверки и реализация Hibernate API проверки автоматически добавляются в проект в качестве временных зависимостей веб-стартера Spring Boot.

Чтобы применить проверку в Spring MVC, необходимо:

Объявите правила проверки классам, которые должны быть проверены: в частности, класс Taco.

Указать, что проверка должна быть выполнена в методах контроллера, которые требуют проверки: в частности, метод processDesign в DesignTacoController() и в OrderController метод processOrder().

Измените представления формы для отображения ошибок проверки.

API проверки предлагает несколько аннотаций, которые можно поместить в свойства объектов домена для объявления правил проверки. Реализация Hibernate API проверки добавляет еще больше аннотаций проверки. Давайте посмотрим, как вы можете применить некоторые из этих аннотаций для проверки представленного Taco или Order.

2.3.1 Определение правил проверки

Для класса Taco необходимо убедиться, что свойство name не пустое и не null и что в списке выбранных ингредиентов есть хотя бы один элемент. В следующем списке показан обновленный класс Taco, который использует @NotNull и @Size для объявления этих правил проверки.

Листинг 2.10 Добавленные проверки в класс Taco

package tacos;


import java.util.List;


import javax.validation.constraints.NotNull;


import javax.validation.constraints.Size; import lombok.Data;



@Data


public class Taco {


@NotNull


@Size(min=5, message="Name must be at least 5 characters long")


private String name;



@Size(min=1, message="You must choose at least 1 ingredient")


private List ingredients;


}

Вы заметите, что в дополнение к требованию, чтобы свойство name не было null, вы объявляете, что оно должно иметь значение длиной не менее 5 символов.

Когда дело доходит до объявления проверки представленных заказов тако, необходимо применить аннотации к классу Order. Для свойств адреса необходимо только убедиться, что пользователь не оставляет пустыми ни одно из полей. Для этого вы будете использовать аннотацию Hibernate Validator @NotBlank.

Однако проверка полей платежей немного более экзотична. Необходимо не только убедиться, что свойство ccNumber не пусто, но и что оно содержит значение, которое может быть допустимым номером кредитной карты. Свойство ccExpiration должно соответствовать формату MM/YY (двухзначные месяц и год). И свойство ccCVV должно быть трехзначным. Для достижения такого рода проверки необходимо использовать несколько других аннотаций Java Bean Validation API и заимствовать аннотацию проверки из коллекции аннотаций Hibernate Validator. В следующем листинге показаны изменения, необходимые для проверки класса Order.

Листинг 2.11 Проверка полей заказа

package tacos;

import javax.validation.constraints.Digits;


import javax.validation.constraints.Pattern;


import org.hibernate.validator.constraints.CreditCardNumber;


import javax.validation.constraints.NotBlank;


import lombok.Data;



@Data


public class Order {


@NotBlank(message="Name is required")


private String name;



@NotBlank(message="Street is required")


private String street;

@NotBlank(message="City is required")


private String city;



@NotBlank(message="State is required")


private String state;



@NotBlank(message="Zip code is required")


private String zip;



@CreditCardNumber(message="Not a valid credit card number")


private String ccNumber;



@Pattern(regexp="^(0[1-9]|1[0-2])([\\/])([1-9][0-9])$", message="Must be formatted MM/YY")


private String ccExpiration;



@Digits(integer=3, fraction=0, message="Invalid CVV")


private String ccCVV;


}

Как видите, свойство ccNumber снабжено аннотацией @CreditCardNumber. Эта аннотация объявляет, что значение свойства должно быть допустимым номером кредитной карты, который проходит проверку алгоритма Luhn (https://en.wikipedia.org/wiki/Luhn_algorithm). Это предотвращает ошибки пользователей и преднамеренно неверные данные, но не гарантирует, что номер кредитной карты фактически принадлежит учетной записи или что номер карты пригоден для оплаты.

К сожалению, нет готовой аннотации для проверки формата MM/YY свойства ccExpiration. Я применил аннотацию @Pattern, предоставив ей регулярное выражение, которое гарантирует, что значение свойства придерживается желаемого формата. Если вам интересно, как расшифровать регулярное выражение, я рекомендую вам ознакомиться со многими онлайн-руководствами по регулярным выражениям, в том числе http://www.regular-expressions.info/. Синтаксис регулярных выражений является темным искусством и, безусловно, выходит за рамки этой книги.

Наконец, свойство ccCVV аннотируется @Digits, чтобы убедиться, что значение содержит ровно три цифры.

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

2.3.2 Выполнение проверок

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

Чтобы проверить отправленное Taco, необходимо добавить аннотацию Java Bean Validation API’s @Valid

к аргументу Taco метода processDesign() у DesignTacoController.

Листинг 2.12 Проверка POST-запроса Taco

@PostMapping


public String processDesign(@Valid Taco design, Errors errors) {


if (errors.hasErrors()) {


return "design";


}


// Save the taco design...


// We'll do this in chapter 3


log.info("Processing design: " + design);


return "redirect:/orders/current";


}

@Valid аннотация говорит Spring MVC, чтобы выполнить проверку на представленном объекте Taco после того, как он привязан к представленным данным формы и перед вызовом метода processDesign(). Если есть какие-либо ошибки проверки, сведения об этих ошибках будут записаны в объект Errors, который передается в processDesign(). Первые несколько строк processDesign() проверяют объект Errors, через его метод hasErrors (), есть ли какие-либо ошибки проверки. Если есть, метод завершается без обработки Taco и возвращает имя представления "design" так, чтобы форма была перерисована.

Для выполнения проверки переданных объектов Order, аналогичные изменения также требуются в методе processOrder() в OrderController.

Листинг 2.13 Проверка -POST-запроса Order

@PostMapping


public String processOrder(@Valid Order order, Errors errors) {


if (errors.hasErrors()) {


return "orderForm";


}


log.info("Order submitted: " + order);


return "redirect:/";


}

В обоих случаях методу будет разрешено обрабатывать отправленные данные при отсутствии ошибок проверки. Если есть ошибки проверки, запрос будет перенаправлен в представление формы, чтобы дать пользователю возможность исправить свои ошибки.

Но как пользователь узнает, какие ошибки требуют исправления? Если вы не отобразите ошибки в форме, пользователю останется только гадать о том, как успешно отправить форму.

2.3.3 Отображение ошибок проверки

Thymeleaf предлагает удобный доступ к объекту Errors через свойство fields и с его атрибутом th:errors. Например, чтобы отобразить ошибки проверки в поле номер кредитной карты, можно добавить элемент , который использует эти ссылки на ошибки в шаблоне формы заказа следующим образом.

Листинг 2.14 Отображение ошибок проверки




th:if="${#fields.hasErrors('ccNumber')}"


th:errors="*{ccNumber}">CC Num Error

Помимо атрибута класса, который можно использовать для стилизации ошибки, чтобы привлечь внимание пользователя, элемент использует атрибут th:if, чтобы решить, отображать ли . Метод hasErrors() свойства fields проверяет наличие ошибок в поле ccNumber. Если да, то будет отрисован.

Атрибут th: errors ссылается на поле ccNumber и, если для этого поля имеются ошибки, он заменит содержимое заполнителя элемента сообщением о результате проверки.

Если вы добавите похожие теги для других полей, вы увидите форму, которая выглядит как на рисунке 2.4, когда вы предоставляете недопустимую информацию. Ошибки указывают, что поля name, city и ZIP code оставлены пустыми и что все поля платежа не соответствуют критериям проверки.


Рисунок 2.4 Отображение ошибок проверки в форме заказа

Теперь контроллеры Taco Cloud не только отображают и записывают входные данные, но и проверяют соответствие информации некоторым основным правилам проверки. Давайте вернемся назад и пересмотрим HomeController из главы 1, глядя на альтернативную реализацию.

2.4 Работа с контроллерами отображения

До сих пор вы написали три контроллера для приложения Taco Cloud. Хотя каждый контроллер служит определенной цели в функциональности приложения, все они в значительной степени следуют одной модели программирования:

Они все аннотированы @Controller, чтобы указать, что они классы контроллеры, которые должны автоматически обнаруживаться при сканировании компонентов Spring и создаваться как bean в контексте приложения Spring.

Все, кроме HomeController, аннотируются @RequestMapping на уровне класса, чтобы определить базовый шаблон запроса, который будет обрабатывать контроллер.

Все они имеют один или несколько методов, аннотированных @GetMapping или@PostMapping, чтобы предоставить сведения о том, какие методы должны обрабатывать какие типы запросов.

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

Листинг 2.15 Объявление контроллера отображения

package tacos.web;



import org.springframework.context.annotation.Configuration;


import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;


import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;



@Configuration


public class WebConfig implements WebMvcConfigurer {



@Override


public void addViewControllers(ViewControllerRegistry registry) {


registry.addViewController("/").setViewName("home");


}


}

Самое важное о WebConfig, то что он реализует интерфейс WebMvcConfigurer. WebMvcConfigurer определяет несколько методов для настройки Spring MVC. Несмотря на то, что это интерфейс, он обеспечивает реализацию всех методов по умолчанию, поэтому вам нужно переопределить только необходимые методы. В этом листинге переопределен метод addViewControllers().

Метод addViewControllers() получает ViewControllerRegistry, который можно использовать для регистрации одного или нескольких контроллеров представления. Здесь вы вызываете addViewController() в реестре, передавая "/", путь, по которому ваш контроллер представлений будет обрабатывать запросы GET. Этот метод возвращает объект регистрации контроллера представлений, на котором вы немедленно вызываете setViewName(), чтобы указать home в качестве представления, на которое должен быть перенаправлен запрос "/".

И вот так просто, вы смогли заменить HomeController несколькими строками в классе конфигурации. Теперь вы можете удалить HomeController, и приложение должно вести себя так же, как и раньше. Единственное другое изменение, необходимое для пересмотра HomeControllerTest из главы 1, удаление ссылки на HomeController из аннотации @WebMvcTest, так чтобы тестовый класс будет компилироваться без ошибок.

Здесь вы создали новый класс конфигурации WebConfig для размещения объявления контроллера представления. Но любой класс конфигурации может реализовать WebMvcConfigurer и переопределить метод addViewController. Например, вы могли бы добавить такое же объявление контроллера представления в начальный класс приложения Taco Cloud, например так:

@SpringBootApplication


public class TacoCloudApplication implements WebMvcConfigurer {


public static void main(String[] args) {


SpringApplication.run(TacoCloudApplication.class, args);


}



@Override


public void addViewControllers(ViewControllerRegistry registry) {


registry.addViewController("/").setViewName("home");


}


}

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

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

2.5 Выбор библиотеки шаблонов представлений

В большинстве случаев выбор библиотеки шаблонов представлений зависит от личных предпочтений. Spring очень гибка и поддерживает множество распространенных вариантов шаблонов. За некоторыми небольшими исключениями, библиотека шаблонов, которую вы выберете, сама не будет иметь представления о том, что она даже работает с Spring (одним из таких исключений является -Spring Security dialect Thymeleaf, о котором мы поговорим в главе 4).

Таблица 2.2 Каталог опции шаблона, поддерживаемые автоконфигурацией Spring Boot.


Шаблоны

Spring Boot starter зависимости


FreeMarker

spring-boot-starter-freemarker


Groovy Templates

spring-boot-starter-groovy-templates


JavaServer Pages (JSP)

Нет (предоставлено Tomcat или Jetty)


Mustache

spring-boot-starter-mustache


Thymeleaf

spring-boot-starter-thymeleaf


Вообще говоря, вы выбираете нужную библиотеку шаблонов представления, добавляете ее в качестве зависимости в сборку и начинаете писать шаблоны в каталоге /templates (в каталоге src/main/resources в проекте, построенном на Maven или Gradle). Spring Boot обнаружит выбранную библиотеку шаблонов и автоматически настроит компоненты, необходимые для обслуживания представлений контроллеров Spring MVC.

Вы уже сделали это с Thymeleaf для приложения Taco Cloud. В главе 1 при инициализации проекта был установлен флажок Thymeleaf. Это привело к Spring Boot Thymeleaf starter-у в файле pom.xml. При запуске приложения автоконфигурация Spring Boot обнаруживает наличие Thymeleaf и автоматически настраивает bean-ы Thymeleaf для вас. Все, что вам нужно было сделать, это начать писать шаблоны в /templates.

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

Например, скажем, вы захотели использовать Mustache вместо Thymeleaf. Не проблема. Просто откройте в проекте pom.xml-файл и замените объявление,


org.springframework.boot


spring-boot-starter-thymeleaf


на:


org.springframework.boot


spring-boot-starter-mustache


Конечно, вам нужно будет убедиться, что вы пишете все шаблоны с синтаксисом Mustache вместо тегов Thymeleaf. Особенности работы с Mustache(или любой из вариантов языка шаблона) выходят далеко за рамки этой книги, но чтобы дать вам представление о том, чего ожидать, вот фрагмент из шаблона Mustache, который будет отображать одну из групп ингредиентов в форме дизайна taco:

Designate your wrap:


{{#wrap}}




{{name}}



{{/wrap}}

Это Mustache эквивалент фрагмента Thymeleaf в разделе 2.1.3. Блок {{#wrap}} (который завершается {{/wrap}}) перебирает коллекцию в атрибуте запроса, ключом которого является wrap, и отображает встроенный HTML для каждого элемента. Теги {{{id}} и {{name}} ссылаются на свойства id и name элемента (который должен быть Ingredient).

Вы заметили в таблице 2.2, что JSP не требует каких-либо специальных зависимостей в сборке. Это потому, что сам контейнер сервлета (Tomcat по умолчанию) реализует спецификацию JSP, таким образом не требуя никаких дополнительных зависимостей.

Но есть проблема, если вы решите использовать JSP. Как оказалось, контейнеры сервлетов Java, включая встроенные контейнеры Tomcat и Jetty, обычно ищут JSP где—то в /WEB-INF. Но если вы создаете приложение в виде исполняемого файла JAR, нет способа удовлетворить это требование. Таким образом JSP является вариантом только, если вы создаете приложение в виде файла WAR и развертывания его в контейнере традиционного сервлета. При создании исполняемого файла JAR необходимо выбрать Thymeleaf, FreeMarker или один из других вариантов из таблицы 2.2.

2.5.1 Кэширование шаблонов

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

Однако эта функция не так полезна во время разработки. Предположим, вы запускаете свое приложение и нажимаете страницу создания тако и решаете внести в нее несколько изменений. При обновлении веб-браузера вам все равно будет показана исходная версия. Единственный способ увидеть изменения-перезапустить приложение, что довольно неудобно.

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

Таблица 2.3 Свойства включения/отключения кэширования шаблонов


Шаблоны

Свойство включения кэширования


FreeMarker

spring.freemarker.cache


Groovy Templates

spring.groovy.template.cache


Mustache

spring.mustache.cache


Thymeleaf

spring.thymeleaf.cache


По умолчанию, все эти свойства имеют значение true, чтобы включить кэширование. Можно отключить кэширование для выбранного шаблонизатора, установив для его свойства cache значение false. Например, чтобы отключить кэширование Thymeleaf, добавьте следующую строку в application.properties:

spring.thymeleaf.cache=false

Единственная загвоздка заключается в том, что перед развертыванием приложения в рабочей среде необходимо удалить эту строку (или задать для нее значение true). Одним из вариантов является установка свойства в профиле. (Мы поговорим о профилях в главе 5.)

Гораздо более простой вариант-использовать DevTools Spring Boot, как мы решили сделать в главе 1. Среди множества полезных функций, предлагаемых DevTools во время разработки, он отключит кэширование для всех библиотек шаблонов, но отключит себя (и, следовательно, включит кэширование шаблонов) при развертывании приложения.

ИТОГ

Spring предлагает мощный веб-фреймворк под названием Spring MVC, который может быть использован для разработки веб-интерфейса для Spring-приложения.

Spring MVC - базовые аннотации, позволяющие создать методы обработки запросов с аннотациями, такие как @RequestMapping, @GetMapping, и @PostMapping.

Большинство методов обработки запросов завершаются возвращением логического имени представления, например шаблона Thymeleaf, на который передается запрос (вместе с любыми данными модели).

Spring MVC поддерживает Java Bean Validation API и реализаций Validation API такие как Hibernate Validator.

View контроллеры могут быть использованы для обработки HTTP-запросов GET, для которых не требуется модели данных или обработки.

В дополнение к Thymeleaf Spring поддерживает различные параметры просмотра, включая FreeMarker, Groovy Templates и Mustache.

Spring in Action Covers Spring 5.0 перевод на русский. Глава 3

3. Работа с данными

Эта глава охватывает:

Используя Spring JdbcTemplate

Вставка данных посредством SimpleJdbcInsert

Объявление -JPA - репозиториев с данными Spring

Большинство приложений предлагают больше, чем просто красивый интерфейс. Хотя пользовательский интерфейс может обеспечить взаимодействие с приложением, именно данные, которые он представляет и хранит, отличают приложения от статических веб-сайтов.

В приложении Taco Cloud необходимо поддерживать информацию об ингредиентах, тако и заказах. Без базы данных для хранения этой информации, приложение не сможет развиваться дальше, чем то, что вы разработали в главе 2.

В этой главе вы добавите сохраняемость данных в приложение Taco Cloud. Вы начнете с использования поддержки Spring для JDBC (Java Database Connectivity) для устранения шаблонного кода. Затем вы переработаете репозитории данных для работы с JPA (Java Persistence API), исключив еще больше кода.

3.1 Чтение и запись данных с помощью JDBC

На протяжении десятилетий реляционные базы данных и SQL занимали лидирующие позиции в области сохранения данных. Несмотря на то, что в последние годы появилось много альтернативных типов баз данных, реляционная база данных по-прежнему является лучшим выбором для хранилища данных общего назначения и вряд ли будет узурпирована в ближайшее время.

Когда дело доходит до работы с реляционными данными, у разработчиков Java есть несколько вариантов. Два наиболее распространенных варианта это JDBC и JPA. Spring поддерживает оба из них с абстракциями, что делает работу с JDBC или JPA проще, чем было бы без Spring. В этом разделе мы сосредоточимся на том, как Spring поддерживает JDBC, а затем рассмотрим поддержку Spring для JPA в разделе 3.2.

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

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

листинг 3.1 Запрос к базе данных без JdbcTemplate

@Override


public Ingredient findOne(String id) {


Connection connection = null;


PreparedStatement statement = null;


ResultSet resultSet = null;


try {


connection = dataSource.getConnection();


statement = connection.prepareStatement(


"select id, name, type from Ingredient");


statement.setString(1, id);


resultSet = statement.executeQuery();


Ingredient ingredient = null;


if(resultSet.next()) {


ingredient = new Ingredient(


resultSet.getString("id"),


resultSet.getString("name"),


Ingredient.Type.valueOf(resultSet.getString("type")));


}


return ingredient;


} catch (SQLException e) {


// ??? Что нужно сделать в этом случае ???


} finally {


if (resultSet != null) {


try {


resultSet.close();


} catch (SQLException e) {}


}


if (statement != null) {


try {


statement.close();


} catch (SQLException e) {}


}


if (connection != null) {


try {


connection.close();


} catch (SQLException e) {}


}


}


return null;


}

Уверяю вас, что где-то в листинге 3.1 есть пара строк, которые запрашивают в базе данных ингредиенты. Но бьюсь об заклад, вам было трудно обнаружить иголку запроса в стоге сена JDBC. Он окружен кодом, который создает соединение, создает оператор и очищает, закрывая соединение, оператор и результирующий набор.

Что еще хуже, что угодно всего может пойти не так при создании соединения или инструкции, или при выполнении запроса. Это требует, чтобы вы поймали SQLException, который может или не может быть полезен в выяснении того, что пошло не так или как решить проблему.

SQLException является проверяемым исключением, которое требует обработки в блоке catch. Но наиболее распространенные проблемы, такие как неспособность создать соединение с базой данных или неправильно набранный запрос, не могут быть решены в блоке catch и, вероятно, будут перепрофилированы для обработки в восходящем направлении. Теперь посмотрим чем отличается код с использованием JdbcTemplate.

листинг 3.2 Запрос к базе данных с JdbcTemplate

private JdbcTemplate jdbc;



@Override


public Ingredient findOne(String id) {


return jdbc.queryForObject(


"select id, name, type from Ingredient where id=?",


this::mapRowToIngredient, id);


}



private Ingredient mapRowToIngredient(ResultSet rs, int rowNum)


throws SQLException {


return new Ingredient(


rs.getString("id"),


rs.getString("name"),


Ingredient.Type.valueOf(rs.getString("type")));


}

Код в листинге 3.2 явно намного проще, чем исходный пример JDBC в листинге 3.1; никаких инструкций или соединений не создается. И, после того, как метод закончен, нет никакой очистки объектов. Наконец, нет никакой обработки исключений, которые не могут быть должным образом обработаны в блоке catch. То, что осталось - это код, который сосредоточился исключительно на выполнении запроса (вызова Jdbctemplate метода queryForObject()) и отображение результатов в объекте ингредиент (в mapRowToIngredient() методе).

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

3.1.1 адаптация домена для сохранения.

При сохранении объектов в базе данных обычно рекомендуется иметь одно поле, которое однозначно идентифицирует объект. В вашем классе ингредиентов уже есть поле id, но вам нужно добавить поля id как в Taco, так и в Order.

Кроме того, может быть полезно знать, когда создан Taco и когда создан Order. Кроме того, к каждому объекту необходимо добавить поле для записи даты и времени сохранения объектов. В следующем списке показаны новые поля id и createdAt, необходимые в классе Taco.

Листинг 3.3 Добавление ID и поля времени в класс Taco

@Data


public class Taco {


private Long id;


private Date createdAt;


...


}

Поскольку Lombok используется для автоматического создания методов доступа во время выполнения, нет необходимости делать что-либо большее, чем объявлять свойства id и createdAt. Они будут иметь соответствующие методы getter и setter по мере необходимости во время выполнения. Подобные изменения требуются и в классе Order, как показано здесь:

@Data


public class Order {


private Long id;


private Date placedAt;


...


}

Опять же, Lombok автоматически генерирует методы метода доступа, поэтому это единственные изменения, необходимые в Order. (Если по какой-то причине вы решили не использовать Lombok, вам нужно будет написать эти методы самостоятельно.)

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

3.1.2 Работа с JdbcTemplate

Перед началом использования JdbcTemplate необходимо добавить его в путь к классам проекта. Это можно легко сделать, добавив в сборку зависимость JDBC starter от Spring Boot:


org.springframework.boot


spring-boot-starter-jdbc


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


com.h2database


h2


runtime


Позже, вы увидите, как настроить приложение для использования внешней базы данных. Но сейчас, давайте перейдем к написанию репозитория, который извлекает и сохраняет данные Ingredient.

ОПРЕДЕЛЕНИЕ РЕПОЗИТОРИЕВ JDBC

Репозиторий Ingredient должен выполнять следующие операции:

Query запрос всех ингредиентов в коллекцию Ingredient объектов

Запрос одного Ingredient по его id

Сохранить объект Ingredient

Следующий интерфейс IngredientRepository определяет эти три операции объявляя методы:

package tacos.data;



import tacos.Ingredient;


public interface IngredientRepository {


Iterable findAll();


Ingredient findOne(String id);


Ingredient save(Ingredient ingredient);


}

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

листинг 3.4 -Начало репозиторий ингредиент с JdbcTemplate

package tacos.data;



import org.springframework.beans.factory.annotation.Autowired;


import org.springframework.jdbc.core.JdbcTemplate;


import org.springframework.jdbc.core.RowMapper;


import org.springframework.stereotype.Repository;


import tacos.Ingredient;



@Repository


public class JdbcIngredientRepository implements IngredientRepository {


private JdbcTemplate jdbc;



@Autowired


public JdbcIngredientRepository(JdbcTemplate jdbc) {


this.jdbc = jdbc;


}


...


}

Как вы можете видеть, JdbcIngredientRepository аннотируется @Repository. Эта аннотация является одной из немногих аннотаций стереотипов, которые определяет Spring, включая @Controller и @Component. Аннотируя JdbcIngredientRepository с помощью @Repository, вы объявляете, что он должен быть автоматически обнаружен при сканировании компонентов Spring и создан как bean в контексте приложения Spring.

Когда Spring создает bean JdbcIngredientRepository, он внедряет JdbcTemplate через аннотированную конструкцию @Autowired. Конструктор назначает JdbcTemplate переменной экземпляра, которая будет использоваться в других методах для запроса и вставки в базу данных. Говоря об остальных методах, давайте взглянем на реализации findAll () и findById ().

Листинг 3.5 запросы к базе данных с JdbcTemplate

@Override


public Iterable findAll() {


return jdbc.query("select id, name, type from Ingredient",


this::mapRowToIngredient);


}



@Override


public Ingredient findOne(String id) {


return jdbc.queryForObject(


"select id, name, type from Ingredient where id=?",


this::mapRowToIngredient, id);


}



private Ingredient mapRowToIngredient(ResultSet rs, int rowNum) throws QLException {


return new Ingredient(


rs.getString("id"),


rs.getString("name"),


Ingredient.Type.valueOf(rs.getString("type")));


}

findAll() и findById() используют JdbcTemplate аналогичным образом. Метод findAll(), ожидающий возврата коллекции объектов, использует JdbcTemplate метод запроса. Метод query() принимает SQL для запроса, а также реализацию Spring-овского RowMapper с целью сопоставления каждой строки в результирующем наборе объекту. findAll() также принимает в качестве окончательного аргумента (аргументов) список любых параметров, необходимых в запросе. Но, в данном случае, нет никаких обязательных параметров.

Метод findById() возвращает только один объект ингредиента, поэтому он использует метод queryForObject() JdbcTemplate вместо query(). queryForObject() работает так же, как query(), за исключением того, что возвращает один объект вместо списка объектов. В этом случае ему дается запрос для выполнения, RowMapper и id Ingredient для выборки, который используется вместо ? в запросе.

Загрузка...