-
Notifications
You must be signed in to change notification settings - Fork 53
Тема 36. Spring MVC
- Паттерн MVC
- Spring-контроллеры
- Шаблонизаторы
- Обзор и подключение Swagger
- Обработка ошибок
- Список литературы/курсов
Статическая страница на HTML не умеет реагировать на действия пользователя.
Для двухстороннего взаимодействия нужны динамические веб-страницы.
MVC — ключ к пониманию разработки динамических веб-приложений, поэтому разработчику нужно знать эту Модель.
MVC расшифровывается как модель-представление-контроллер (от англ. model-view-controller).
Это способ организации кода, который предполагает выделение блоков, отвечающих за решение разных задач.
Один блок отвечает за данные приложения, другой отвечает за внешний вид, а третий контролирует работу приложения.
Компоненты MVC:
Модель (model) — этот компонент отвечает за данные, а также определяет структуру приложения.
Например, если необходимо создать To-Do приложение, код компонента model будет определять список задач и отдельные задачи.
Это самая независимая часть системы. Настолько независимая, что она не должна ничего знать о модулях Вид и Контроллер.
Модель настолько независима, что ее разработчики могут практически ничего не знать о Виде и Контроллере.
Представление (view) — этот компонент отвечает за взаимодействие с пользователем.
То есть код компонента view определяет внешний вид приложения и способы его использования.
Основное предназначение Представления — предоставлять информацию из Модели в удобном для восприятия пользователя формате.
Основное ограничение Представления — он никак не должен изменять Модель.
Контроллер (controller) — этот компонент отвечает за связь между model и view.
Код компонента controller определяет, как сайт реагирует на действия пользователя.
По сути, это мозг MVC-приложения.
Основное предназначение Контроллера — обрабатывать действия пользователя.
Именно через Контроллер пользователь вносит изменения в Модель. Точнее в данные, которые хранятся в Модели.
Разберем паттерн MVC на примере магазина сэндвичей.
Представьте, что вы пришли в магазин или кафе, где можно заказать сэндвич. В меню есть бутерброды с тунцом, индейкой и ветчиной. Вы заказываете сэндвич с индейкой.
Продавец или бармен с полуслова понимает вас. Он поворачивается в сторону кухни и говорит поварам, чтобы они приготовили сэндвич с индейкой.
У поваров под рукой есть разные продукты: тунец, индейка, ветчина, сыр, листья салата и другие ингредиенты, которые добавляют в бутерброды. Они выбирают только то, что нужно для вашего сэндвича с индейкой. Вы получаете свой заказ.
Покупку бутерброда можно описать через MVC:
Модель: кухня, на которой повар делает сэндвич.
Представление: готовый бутерброд, который вы с удовольствием едите.
Контроллер: продавец или бармен, который принимает заказ и передаёт его на кухню.

Из всего этого можно сделать вполне логичный вывод. Сложную систему нужно разбивать на модули.
Рассмотрим пример:
Ключевая идея MVC состоит в том, что любое приложение с пользовательским интерфейсом в первом приближении можно разбить на 2 модуля: модуль, отвечающий за реализацию бизнес-логики приложения, и пользовательский интерфейс.
В первом модуле будет реализован основной функционал приложения.
Данный модуль будет ядром системы, в котором реализуется Модель предметной области приложения.
В концепции MVC данный модуль будет Моделью.
Во втором модуле будет реализован весь пользовательский интерфейс, включая отображение данных пользователю и логику взаимодействия пользователя с приложением.
Основная цель такого разделения — сделать так, чтобы ядро системы (Модель в терминологии MVC) могла независимо разрабатываться и тестироваться.
Архитектура приложения после подобного разделения будет выглядеть следующим образом:

Далее необходимо используя шаблон Наблюдатель, добиться еще большей независимости Модели, а также синхронизации пользовательских интерфейсов.
Здесь мы преследуем 2 цели:
- Добиться еще большей независимости
Модели. - Синхронизировать пользовательские интерфейсы.
Понять, что подразумевается под синхронизацией пользовательских интерфейсов, поможет следующий пример.
Предположим, мы покупаем билет в кино через интернет и видим количество свободных мест в кинотеатре. Одновременно с нами покупать билет в кино может кто-то еще. Если этот кто-то купит билет раньше нас, нам бы хотелось увидеть, что количество свободных мест на наш сеанс уменьшилось.
А теперь поразмышляем о том, как это может быть реализовано внутри программы.
Предположим, у нас есть ядро системы (Модель) и интерфейс (веб страница, на которой осуществляем покупку).
На сайте 2 пользователя одновременно выбирают место.
Первый пользователь купил билет. Второму пользователю необходимо отобразить на странице эту информацию.
Как это происходит:
Если мы из ядра системы будем обновлять интерфейс, наше ядро, наша Модель, будет зависима от интерфейса.
При разработке и тестировании Модели придется держать в голове различные способы обновления интерфейса.
Чтобы достичь этого, необходимо реализовать шаблон Наблюдатель.
С его помощью Модель рассылает уведомления об изменениях всем подписчикам. Интерфейс, являясь таким подписчиком, получит уведомление и обновится.
Шаблон Наблюдатель позволяет Модели с одной стороны информировать интерфейс (представление и контроллер) о том, что в ней произошли изменения, а с другой — фактически ничего о них “не знать”, и тем самым оставаться независимой.
С другой стороны, это позволит синхронизировать пользовательские интерфейсы.
Разделение интерфейса на Представление и Контроллер
Продолжаем делить приложение на модули, но уже на более низком уровне иерархии.
На этом шаге пользовательский интерфейс (который был выделен в отдельный модуль на шаге 1) делится на Представление и Контроллер.
Сложно провести строгую черту между Представлением и Контроллером.
Если говорить о том, что Представление — это то, что видит пользователь, а Контроллер — это механизм, благодаря которому пользователь может взаимодействовать с системой, можно обнаружить некоторое противоречие.
Элементы управления, например, кнопки на веб-странице или виртуальная клавиатура на экране телефона, это по сути часть Контроллера.
Но они так же видны пользователю, как и любая часть Представления.
Здесь скорее речь идет о функциональном разделении. Основная задача пользовательского интерфейса — обеспечить взаимодействие пользователя с системой. Это означает, что у интерфейса всего 2 функции:
- выводить и удобно отображать пользователю информацию о системе;
- вводить данные и команды пользователя в систему (передавать их системе);
Данные функции и определяют то, как нужно делить интерфейс на модули.
В итоге, архитектура системы выглядит так:

Итак, у нас появилось приложение из трех модулей, которые называются Модель, Представление и Контроллер.
Таким образом:
- Следуя принципам
MVC, систему нужно разделять на модули. - Самым важным и независимым модулем должна быть
Модель. -
Модель— ядро системы. Нужна возможность разрабатывать и тестировать ее независимо от интерфейса. Для этого на первом шаге сегрегации системы нужно разделить ее намодельиинтерфейс. Далее, с помощью шаблонаНаблюдатель, укрепляеммодельв ее независимости и получаем синхронизацию пользовательских интерфейсов. Третьим шагом делим интерфейс наКонтроллериПредставление. Все, что на ввод информации от пользователя в систему — это вКонтроллер. Все что на вывод информации от системы к пользователю — это вПредставление.
Взаимосвязь Представления и Контроллера с Моделью
Когда пользователь вводит информацию через Контроллер, он тем самым вносит изменения в модель. По крайней мере, пользователь вносит изменения в данные Модели.
Когда пользователь получает информацию через элементы интерфейса (через Представление), пользователь получает информацию о данных модели.
Как это происходит? Посредством чего Представление и Контроллер взаимодействуют с Моделью?
Ведь не может быть так, что классы Представления напрямую используют методы классов Модели для чтения/записи данных, иначе ни о какой независимости Модели не может быть и речи.
Модель представляет тесно связанный между собой набор классов, к которым, по-хорошему, ни у Представления, ни у Контроллера не должно быть доступа.
Для связи Модели с Представлением и Контроллером необходимо реализовать шаблон проектирования Фасад.
Фасад Модели будет той самой прослойкой между Моделью и интерфейсом, через которую Представление получает данные в удобном формате, а Контроллер изменяет данные, вызывая нужные методы фасада.
Схематично, в итоге, все будет выглядеть так:

Преимущества MVC
Основная цель следования принципам MVC — отделить реализацию бизнес-логики приложения (Модели) от ее визуализации (Представления).
- Такое разделение повысит возможность повторного использования кода.
- Польза применения
MVCнаиболее наглядна в случаях, когда пользователю нужно предоставлять одни и те же данные в разных формах. Например, в виде таблицы, графика или диаграммы (используя различные виды). При этом, не затрагивая реализацию видов, можно изменить реакции на действия пользователя (нажатие мышью на кнопке, ввод данных). - Если следовать принципам
MVC, можно упростить написание программ, повысить читаемость кода, сделать легче расширение и поддержку системы в будущем.
Шаблонизаторы являются отличным инструментом для создания веб-приложений с более чистым и простым кодом. Рассмотрим наиболее популярные.
Mustache.JS Mustache.JS - это логический шаблонный синтаксис. Его можно использовать для HTML, конфигурационных файлов, исходного кода - чего угодно. Он работает путем расширения тегов в шаблоне, используя значения, предоставленные в хэше или объекте.
Transparency Transparency - это минимальный шаблонизатор для jQuery. Он отображает объекты JSON на элементы DOM с нулевой конфигурацией. Просто вызовите .render().
Hogan.js Hogan.js - это шаблонизатор JS 3.4k, разработанный в Twitter. Используйте его как часть вашего упаковщика ресурсов для предварительной компиляции шаблонов или включите его в браузер для обработки динамических шаблонов. Если вы разрабатываете с Node.js, просто используйте NPM, чтобы добавить пакет Hogan.
HandlebarsJS HandlebarsJS - обеспечивает необходимую мощность, позволяющую эффективно создавать семантические шаблоны без каких-либо разочарований. Handlebars в значительной степени совместим с шаблонами Mustache. В большинстве случаев можно поменять Mustache с помощью Handlebars и продолжить использовать текущие шаблоны.
Jade Language Jade - это движок HTML шаблонов, в основном используемый для серверных шаблонов в NodeJS.
Nunjucks Nunjucks - это богатый и мощный язык шаблонов с наследованием блоков, автоэкранированием, макросами, асинхронным управлением и многим другим.
Swagger - это стандартизированная и полная структура для генерации, описания, вызова и визуализации веб-сервисов RESTful.
Это удобный инструмент для создания документации API, который помогает разработчикам сэкономить время.
Он предлагает несколько решений для интеграции в проект и формирования интерактивной версии документации, с которой будет удобно взаимодействовать другим разработчикам, внешним пользователям, клиентам.
Swagger — это набор инструментов, которые помогают описывать API.
Благодаря ему пользователи и машины лучше понимают возможности REST API без доступа к коду.
С помощью Swagger можно быстро создать документацию и отправить ее другим разработчикам или клиентам.
Общая цель - позволить клиенту и файловой системе обновляться с той же скоростью, что и сервер. Метод, параметры и модель файла тесно интегрированы в код на стороне сервера, что позволяет API всегда оставаться в синхронизации.
Основные подходы
Swagger предлагает два основных подхода к генерированию документации:
- Автогенерация на основе кода.
- Самостоятельная разметка-написание.
Первый подход проще. Мы добавляем зависимости в проект, конфигурируем настройки и получаем документацию. Сам код из-за этого может стать менее читабельным, документация тоже не будет идеальной. Но задача минимум решена — код задокументирован.
Чтобы пользоваться вторым подходом, нужно знать синтаксис Swagger.
Описания можно готовить в формате YAML/JSON. Можно упростить эту задачу, используя Swagger Editor.
Конечно, второй подход позволяет сделать документацию более качественной и кастомной для каждого конкретного проекта и его особенностей, к тому же все не так сложно как может показаться, это потребует минимальных дополнительных усилий.
Swagger Core Это Java-реализация спецификации. Для ее использования потребуется:
- Java 8 и выше
- Apache Maven 3.0.4 и выше
- Jackson 2.4.5 и выше
Для его внедрения в проект используются две зависимости.
Приведем примеры:
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.1.11</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.2</version>
</dependency>
Другой способ — настроить maven-плагин. Тогда описания при сборке проекта будут генерироваться в файл YAML. Пример:
<plugin>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-maven-plugin</artifactId>
<version>0.3</version>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<apiDocsUrl>http://localhost:3080/v3/api-docs</apiDocsUrl>
<outputFileName>openapi.yaml</outputFileName>
<outputDir>${project.build.directory}</outputDir>
</configuration>
</plugin>Для конфигурации нужны еще два бина (beans). В них мы описываем название приложения, версию API, добавляем другие важные данные.
После настройки конфигурации мы получим аннотации, которые можно использовать для документирования кода.
| Аннотация | Использование |
|---|---|
| @Operation | Для описания операции или метода HTTP |
| @Parameter | Для представления одного параметра в операции |
| @RequestBody | Для представления тела запроса в операции |
| @ApiResponse | Для представления тела ответа в операции |
| @Tag | Для представления тегов операции или определения OpenAPI |
| @Server | Для представления серверов операции или определения OpenAPI |
| @Callback | Для описания набора запросов |
| @Link | Для представления ссылки времени разработки для ответа |
| @Schema | Для определения входных и выходных данных |
| @ArraySchema | Для определения входных и выходных данных для типов массивов |
| @Content | Для представления схемы и примеров для мультимедиа |
| @Hidden | Для скрытия ресурса, операции или свойстваSwagger Codegen |
Преимущества Swagger Codegen:
- Генерирование серверов — позволяет избавиться от рутины, создавая шаблонный код на 20+ языках.
- Упрощенное генерирование SDK — можно создавать клиентские наборы на 40+ языках для быстрой интеграции с вашим API.
- Постоянное обновление инструментов.
Чтобы добавить Swagger Codegen в проект, используйте зависимость:
<dependency>
<groupId>io.swagger.codegen.v3</groupId>
<artifactId>swagger-codegen-maven-plugin</artifactId>
<version>3.0.24</version>
</dependency>
Как и в случае с Swagger Core, можно настроить maven-плагин для генерации клиента или мок-сервера.
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>3.3.4</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<generatorName>spring</generatorName>
<inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec>
<output>${project.build.directory}/generated-sources</output>
<apiPackage>com.api</apiPackage>
<modelPackage>com.model</modelPackage>
<supportingFilesToGenerate>
ApiUtil.java
</supportingFilesToGenerate>
<configOptions>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<artifactVersion>${project.version}</artifactVersion>
<delegatePattern>true</delegatePattern>
<sourceFolder>swagger</sourceFolder>
<library>spring-mvc</library>
<interfaceOnly>true</interfaceOnly>
<useBeanValidation>true</useBeanValidation>
<dateLibrary>java8</dateLibrary>
<java8>true</java8>
</configOptions>
<ignoreFileOverride>${project.basedir}/.openapi-generator-ignore</ignoreFileOverride>
</configuration>
</execution>
</executions>
</plugin>Swagger Codegen предоставляет следующие команды:
- Config-help — помощь с настройкой выбранного языка.
- Generate — генерирование кода с выбранным генератором.
- Help — вывод справочной информации про openapi-generator.
- List — список доступных генераторов.
- Meta — генератор набора шаблонов и настроек Codegen. Использует информацию о выбранном языке.
- Validate — проверка спецификации.
- Version — отображение используемой версии.
- Самые полезные команды — generate и validate. Первая позволяет создать клиент, а вторая — проверить его соответствие спецификации.
- Посмотреть полный список возможностей можно с помощью команды help после запуска jar-файла.
Swagger UI Swagger UI визуализирует ресурсы OpenAPI и взаимодействие с ними без отображения логики реализации. Этот инструмент берет спецификацию и превращает ее в интерактивный проект, где можно выполнять разные запросы для тестирования API.
Преимущества Swagger UI:
- Нет зависимостей. Интерфейс доступен в любой среде разработки.
- Полная поддержка браузерами. Удобное взаимодействие для других разработчиков и клиентов. Можно проверить все операции, предоставляемые API, даже не имея специальных знаний.
- Простая навигация. Ресурсы и конечные точки представлены в виде аккуратно распределенного на категории списка.
- Можно настраивать стиль и пользовательский интерфейс так, как вам хочется.
- Пользовательский интерфейс
Swaggerполностью размещен в SwaggerHub. Вы можете написать и визуализировать API или импортировать существующие определения для создания интерактивного интерфейса, размещенного в облаке. Благодаря встроенной интерактивности SwaggerHub позволяет безопасно предоставлять доступ к документации внешним пользователям или другим разработчикам.
Swagger Editor
Это онлайн-редактор для изменения и проверки API внутри браузера. Позволяет просматривать документацию в реалтайме.
С его помощью можно создать описания, а затем использовать их с полным набором инструментов для генерации документации.
ПреимуществаSwagger Editor:
- Работает в любой среде разработки, локально и в сети.
- Дает обратную связь — показывает ошибки в синтаксисе по мере написания, проверяя его на соответствие
OpenAPI. - Позволяет мгновенно визуализировать
APIчерезSwagger UI. - Помогает писать документацию быстрее благодаря интеллектуальному автозаполнению.
- Предоставляет гибкие инструменты для настройки конфигурации — от темы оформления до межстрочного интервала.
- Предлагает создание серверных заглушек и клиентских библиотек для вашего
APIна всех популярных языках.
Преимущества использования Swagger
-
Swaggerможет создать интерактивную консоль API, которую разработчики могут использовать для быстрого изучения и опробования API. -
Swaggerможет генерировать клиентский SDK-код для реализации на различных платформах. - Файлы
Swaggerмогут автоматически генерироваться из комментариев кода на многих различных платформах. -
Swaggerимеет сильное сообщество со многими влиятельными участниками.
Самый простой способ обработки ошибок-это ответить соответствующим кодом состояния.
Некоторые распространенные коды ответов включают в себя:
-
400 Плохой запрос — Клиент отправил недопустимый запрос — например, отсутствует требуемое тело запроса или параметр.
-
401 Неавторизованный — Клиенту не удалось пройти аутентификацию на сервере.
-
403 Forbidden — Клиент аутентифицирован, но не имеет разрешения на доступ к запрошенному ресурсу.
-
404 Не найден — Запрошенный ресурс не существует.
-
412 Ошибка предварительного условия — одно или несколько условий в полях заголовка запроса оцениваются как ложные.
-
500 Внутренняя ошибка сервера — На сервере произошла общая ошибка.
-
503 Услуга Недоступна — Запрошенная услуга недоступна.
-
Будучи базовыми, эти коды позволяют клиенту понять широкий характер возникшей ошибки. Например, мы знаем, если получаем ошибку 403, что у нас нет разрешений на доступ к запрошенному ресурсу.
Во многих случаях, однако, необходимо предоставить дополнительные детали в наших ответах.
500 ошибок сигнализируют о том, что при обработке запроса на сервере возникли некоторые проблемы или исключения. Как правило, эта внутренняя ошибка не является делом нашего клиента.
Следовательно, чтобы свести к минимуму такого рода ответы клиенту, мы должны усердно пытаться обрабатывать или улавливать внутренние ошибки и отвечать другими соответствующими кодами состояния, где это возможно. Например, если исключение возникает из-за того, что запрошенный ресурс не существует, мы должны выставить это как ошибку 404, а не 500.
Это не означает, что 500 никогда не должны быть возвращены, только то, что он должен использоваться для непредвиденных условий – таких как отключение службы – которые мешают серверу выполнить запрос.
Нашел ошибку или есть что добавить? Обязательно напиши мне об этом, рад любой обратной связи. А лучше сразу закидывай пулл-реквест!