24. Расскажите про паттерн MVC, как он реализован в Spring?
UNKNOWNMVC (Model-View-Controller)
Это шаблон проектирования программного обеспечения, который делит программную логику на три отдельных, но взаимосвязанных компонента: модель, представление и контроллер — таким образом, что модификация каждого компонента может осуществляться независимо.
Модель (Model) предоставляет данные и реагирует на команды контроллера, изменяя своё состояние. Она содержит всю бизнес-логику приложения.
Представление (View) отвечает за отображение пользователю данных из модели в нужном формате.
Контроллер (Controller) содержит код, который отвечает за обработку действий пользователя и обменивается данными с моделью (любое действие пользователя в системе обрабатывается в контроллере).
Основная цель следования принципам MVC — отделить реализацию бизнес-логики приложения (модели) от ее визуализации (вида). Такое разделение повысит возможность повторного использования кода.
Польза применения MVC наиболее наглядна в случаях, когда пользователю нужно предоставлять одни и те же данные в разных формах. Например, в виде таблицы, графика или диаграммы (используя различные виды). При этом, не затрагивая реализацию видов, можно изменить реакции на действия пользователя (нажатие мышью на кнопке, ввод данных).
Spring Web MVC
Spring MVC - это оригинальный веб-фреймворк, основанный на Servlet API, предназначенный для создания веб-приложений на языке Java, с использованием двух самых популярных шаблонов проектирования - Front controller и MVC.
Front controller (Единая точка входа) - паттерн, где центральный сервлет, DispatcherServlet, принимает все запросы и распределяет их между контроллерами, обрабатывающими разные URL.
Spring MVC реализует четкое разделение задач, что позволяет нам легко разрабатывать и тестировать наши приложения. Данные задачи разбиты между разными компонентами: Dispatcher Servlet, Controllers, View Resolvers, Views, Models, ModelAndView, Model and Session Attributes, которые полностью независимы друг от друга, и отвечают только за одно направление.
Поэтому MVC дает нам довольно большую гибкость. Он основан на интерфейсах (с предоставленными классами реализации), и мы можем настраивать каждую часть фреймворка с помощью пользовательских интерфейсов.
Основные интерфейсы для обработки запросов:
HandlerMapping. По запросу определяет, какие перехватчики (interceptors) с пре- и пост- процессорной обработкой запроса должны отработать, а затем решает, какому контроллеру (обработчику) нужно передать данный запрос на исполнение. Процесс их определения основан на некоторых критериях, детали которых зависят от реализации HandlerMapping.
Двумя основными реализациями HandlerMapping являются RequestMappingHandlerMapping (который поддерживает аннотированные методы @RequestMapping) и SimpleUrlHandlerMapping (который поддерживает явную регистрацию путей URI для обработчиков).
HandlerAdapter. Помогает DispatcherServlet вызвать обработчик, сопоставленный с запросом. Для вызова аннотированного контроллера необходимо прочитать аннотации над методами контроллера и принять решение. Основная цель HandlerAdapter - избавить DispatcherServlet от этой рутины.
ViewResolver. Сопоставляет имена представлений, возвращаемых методами контроллеров, с фактическими представлениями (html-файлами).
View. Отвечает за возвращение ответа клиенту в виде текстов и изображений.
Используются встраиваемые шаблонизаторы (Thymeleaf, FreeMarker и т.д.), так как у Spring нет родных. Некоторые запросы могут идти прямо во View, не заходя в Model, другие проходят через все слои.
LocaleResolver. Определение часового пояса и языка клиента для того, чтобы предложить представления на его языке.
MultipartResolver. Обеспечивает Upload — загрузку на сервер локальных файлов клиента. По умолчанию этот интерфейс не включается в приложении и необходимо указывать его в файле конфигурации. После настройки любой запрос о загрузке будет отправляться этому интерфейсу.
FlashMapManager. Сохраняет и извлекает «входной» и «выходной» FlashMap, который можно использовать для передачи атрибутов из одного запроса в другой, обычно через редирект.
Ниже приведена последовательность событий, соответствующая входящему HTTP- запросу:
- После получения HTTP-запроса DispatcherServlet обращается к интерфейсу HandlerMapping, который определяет, какой Контроллер (Controller) должен быть вызван, после чего HandlerAdapter, отправляет запрос в нужный метод Контроллера.
- Контроллер принимает запрос и вызывает соответствующий служебный метод, основанный на GET, POST и т.д. Вызванный метод формирует данные Модели (например, набор данных из БД) и возвращает их в DispatcherServlet вместе с именем Представления (View) (как правило имя html-файла).
- При помощи интерфейса ViewResolver DispatcherServlet определяет, какое Представление нужно использовать на основании полученного имени и получает в ответе имя представления View.
- Если это REST-запрос на сырые данные (JSON/XML), то DispatcherServlet сам его отправляет;
- Если обычный запрос, то DispatcherServlet отправляет данные Модели в виде атрибутов в Представление (View) - шаблонизаторы Thymeleaf, FreeMarker и т.д., которые сами отправляют ответ.

Как мы видим все действия происходят через один единственный DispatcherServlet.
Сконфигурировать наше Spring MVC-приложение мы можем с помощью Java-config, добавив зависимость spring-webmvc и установив над классом конфигурации @EnableWebMvc, которая применит дефолтные настройки - зарегистрирует некоторые специальные бины из Spring MVC и адаптирует их к нашим бинам. Но, если требуется тонкая настройка, то мы можем имплементировать интерфейс WebMvcConfigurer и переопределить необходимые методы.
Теперь нужно зарегистрировать конфигурацию в Spring Context это позволит сделать созданный нами класс MyWebAppInitializer, который нужно унаследовать от AbstractAnnotationConfigDispatcherServletInitializer, и передать в его методы классы нашей конфигурации RootConfig.class и App1Config.class:
public class MyWebAppInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletCon figClasses() {
return new Class<?>[] { App1Config.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { \"/*\" };
}
}
Своими внутренними методами он создает два экземпляра WebApplicationContext в виде объектов класса AnnotationConfigWebApplicationContext.
Если же у нас только один класс конфигурации, то его нужно передать в метод getRootConfigClasses(), а getServletConfigClasses() должен возвращать null.
Предыдущий вопрос: 23. Как спринг работает с транзакциями? Расскажите про аннотацию @Transactional
Следующий вопрос: 25. Что такое ViewResolver?
Все вопросы по теме: список
Все темы: список
Вопросы/замечания/предложения/нашли ошибку: напишите мне