Фреймворк Spring MVC обеспечивает архитектуру паттерна Model — View — Controller (Модель — Отображение (далее — Вид) — Контроллер) при помощи слабо связанных готовых компонентов. Паттерн MVC разделяет аспекты приложения (логику ввода, бизнес-логику и логику UI), обеспечивая при этом свободную связь между ними.

  • Model (Модель) инкапсулирует (объединяет) данные приложения, в целом они будут состоять из POJO («Старых добрых Java-объектов», или бинов).
  • View (Отображение, Вид) отвечает за отображение данных Модели, — как правило, генерируя HTML, которые мы видим в своём браузере.
  • Controller (Контроллер) обрабатывает запрос пользователя, создаёт соответствующую Модель и передаёт её для отображения в Вид.

DispatcherServlet


Вся логика работы Spring MVC построена вокруг DispatcherServlet, который принимает и обрабатывает все HTTP-запросы (из UI) и ответы на них. Рабочий процесс обработки запроса DispatcherServlet'ом проиллюстрирован на следующей диаграмме:



Ниже приведена последовательность событий, соответствующая входящему HTTP-запросу:

  • После получения HTTP-запроса DispatcherServlet обращается к интерфейсу HandlerMapping, который определяет, какой Контроллер должен быть вызван, после чего, отправляет запрос в нужный Контроллер.
  • Контроллер принимает запрос и вызывает соответствующий служебный метод, основанный на GET или POST. Вызванный метод определяет данные Модели, основанные на определённой бизнес-логике и возвращает в DispatcherServlet имя Вида (View).
  • При помощи интерфейса ViewResolver DispatcherServlet определяет, какой Вид нужно использовать на основании полученного имени.
  • После того, как Вид (View) создан, DispatcherServlet отправляет данные Модели в виде атрибутов в Вид, который в конечном итоге отображается в браузере.

Все вышеупомянутые компоненты, а именно, HandlerMapping, Controller и ViewResolver, являются частями интерфейса WebApplicationContext extends ApplicationContext, с некоторыми дополнительными особенностями, необходимыми для создания web-приложений.

Конфигурирование


Вам будет необходимо связать (замапить) запросы, которые Вы хотите обработать при помощи DispatcherServlet, используя мапинг URL в файле web.xml. Ниже приведён пример объявления и мапинга DispatcherServlet'а HelloWeb:

<web-app id = "WebApp_ID" version = "2.4"
   xmlns = "http://java.sun.com/xml/ns/j2ee" 
   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation = "http://java.sun.com/xml/ns/j2ee 
   http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    
   <display-name>Spring MVC Application</display-name>
   
   <servlet>
      <servlet-name>HelloWeb</servlet-name>
      <servlet-class>
         org.springframework.web.servlet.DispatcherServlet
      </servlet-class>
      <load-on-startup>1</load-on-startup>
   </servlet>

   <servlet-mapping>
      <servlet-name>HelloWeb</servlet-name>
      <url-pattern>*.jsp</url-pattern>
   </servlet-mapping>

</web-app>

Файл web.xml будет находиться в каталоге WebContent/WEB-INF. После инициализации HelloWeb, фреймворк попытается загрузить контекст приложения из файла с именем [servlet-name]-servlet.xml, находящегося в каталоге WebContent/WEB-INF. В нашем случае, это будет HelloWeb-servlet.xml.

Далее, тэг <servlet-mapping> указывает, какие веб-адреса обрабатываются каким DispatcherServlet'ом. В нашем случае, все HTTP-запросы, заканчивающиеся на ".jsp", будут обработаны HelloWeb.

Если Вы не хотите использовать [servlet-name]-servlet.xml / WebContent/WEB-INF в качестве файла и директории по умолчанию, Вы можете настроить имя файла и директорию, добавив слушатель сервлета (servlet listener) ContextLoaderListener в web.xml, как показано ниже:

<web-app...>

   <!-------- DispatcherServlet definition goes here----->
   ....
   <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/HelloWeb-servlet.xml</param-value>
   </context-param>

   <listener>
      <listener-class>
         org.springframework.web.context.ContextLoaderListener
      </listener-class>
   </listener>
   
</web-app>

Теперь давайте проверим конфигурацию для HelloWeb-servlet.xml, размещённую в каталоге WebContent/WEB-INF:

<beans xmlns = "http://www.springframework.org/schema/beans"
   xmlns:context = "http://www.springframework.org/schema/context"
   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation = "http://www.springframework.org/schema/beans     
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/context 
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">

   <context:component-scan base-package = "com.tutorialspoint" />

   <bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver">
      <property name = "prefix" value = "/WEB-INF/jsp/" />
      <property name = "suffix" value = ".jsp" />
   </bean>

</beans>

Ниже приведены важные моменты в HelloWeb-servlet.xml:

  • Файл [servlet-name]-servlet.xml будет использоваться для создания обозначенных бинов, с переопределением определений каждого бина, определённых с тем же именем в глобальной области.
  • Тэг <context:component-scan...> будет использован для для активации функции сканирования аннотаций Spring MVC, позволяющей использовать аннотации, такие как Controller, @RequestMapping и проч.
  • Имена Видов (View) будут определяться при помощи InternalResourceViewResolver. В соответствии с указанным выше правилом, Вид с именем hello будет реализован по адресу /WEB-INF/jsp/hello.jsp

В следующем разделе будет показано, как создавать Controller, Model и View.

Определение Контроллера


DispatcherServlet отправляет запрос контроллерам для выполнения определённых функций. Аннотация @Controllerannotation указывает, что конкретный класс является контроллером. Аннотация @RequestMapping используется для мапинга (связывания) с URL для всего класса или для конкретного метода обработчика.

@Controller
@RequestMapping("/hello")
public class HelloController { 
   @RequestMapping(method = RequestMethod.GET)
   public String printHello(ModelMap model) {
      model.addAttribute("message", "Hello Spring MVC Framework!");
      return "hello";
   }
}

Аннотация Controller определяет класс как Контроллер Spring MVC. В первом случае, @RequestMapping указывает, что все методы в данном Контроллере относятся к URL-адресу "/hello". Следующая аннотация @RequestMapping(method = RequestMethod.GET) используется для объявления метода printHello() как дефолтного метода для обработки HTTP-запросов GET (в данном Контроллере). Вы можете определить любой другой метод как обработчик всех POST-запросов по данному URL-адресу.

Вы можете написать вышеуказанный Контроллер по-другому, указав дополнительные атрибуты для аннотации @RequestMapping следующим образом:

@Controller
public class HelloController {
   @RequestMapping(value = "/hello", method = RequestMethod.GET)
   public String printHello(ModelMap model) {
      model.addAttribute("message", "Hello Spring MVC Framework!");
      return "hello";
   }
}

Атрибут «value» указывает URL, с которым мы связываем данный метод (value = "/hello"), далее указывается, что этот метод будет обрабатывать GET-запросы (method = RequestMethod.GET). Также, нужно отметить важные моменты в отношении приведённого выше контроллера:

  • Вы определяете бизнес-логику внутри связанного таким образом служебного метода. Из него Вы можете вызывать любые другие методы.
  • Основываясь на заданной бизнес-логике, в рамках этого метода Вы создаёте Модель (Model). Вы можете добавлять аттрибуты Модели, которые будут добавлены в Вид (View). В примере выше мы создаём Модель с атрибутом «message».
  • Данный служебный метод возвращает имя Вида в виде строки String. В данном случае, запрашиваемый Вид имеет имя «hello».

Создание Вида (JSP)


Spring MVC поддерживает множество типов Видов для различных технологий отображения страницы. В том числе — JSP, HTML, PDF, Excel, XML, Velocity templates, XSLT, JSON, каналы Atom и RSS, JasperReports и проч. Но чаще всего используются шаблоны JSP, написанные при помощи JSTL.

Давайте напишем простой Вид «hello» в /WEB-INF/hello/hello.jsp:

<html>
   <head>
      <title>Hello Spring MVC</title>
   </head>
   
   <body>
      <h2>${message}</h2>
   </body>
</html>

В данном случае, переменная ${message} выводит тот самый атрибут, установленный нами в Контроллере. Внутри Вида Вы можете отобразить любое количество атрибутов.

Примеры реализации фреймворка Spring MVC


Основываясь на приведённых выше концепциях, предлагаю выполнить несколько важных уроков, которые в дальнейшем помогут нам создавать приложения Spring Web:

Spring MVC Hello World Example
Пример, разъясняющий написание простейшего приложения Hello World.

Spring MVC Form Handling Example
В этом примере объясняется, как написать приложение Spring Web с помощью форм HTML, отправить данные контроллеру и отобразить обработанный результат.

Spring Page Redirection Example
Используем функцию редиректа страниц.

Spring Static Pages Example
Получаем доступ к статическим страницам вместе с динамическими.

Spring Exception Handling Example
Обработка исключений.

Комментарии (9)


  1. Kodeks
    31.08.2017 07:18
    +3

    Свежо


  1. Lure_of_Chaos
    31.08.2017 07:18
    -1

    Действительно, если нам нужен только Spring MVC, то данный подход вполне оправдан — я имею ввиду использование web.xml, [servlet-name]-servlet.xml, и, кмк, эти файлы уже давно мусор.
    Но ведь уже давно существует Spring Boot с его конструктором start.spring.io, где достаточно написать несколько строчек в application.properties, чтобы все заработало.


    1. siziyman
      31.08.2017 10:52

      Boot, конечно, частенько бывает удобен, но иногда проще сконфигурировать что-то руками — всё-таки он очень много забот берёт на себя, и иногда это из плюса превращается в минус, когда что-то идёт не так, а понять, в чём именно проблема, не удаётся.


      1. Lure_of_Chaos
        01.09.2017 08:31

        Частично соглашусь, что, не зная умолчаний конфигурации (либо вообще имея мало опыта с Boot), легко проморгать причину и начать копать решение проблемы не в ту степь, особенно в случае «понять, в чём именно проблема» ну совсем-совсем никак не удаётся.
        Однако, (о чем был мой комментарий) зачастую легче перекрыть часть конфигурации, чем в обязательном порядке расписывать ее всю.


  1. siziyman
    31.08.2017 11:52
    +1

    Простите, но зачем на Хабре перевод банальнейшего неинформативного туториала?


    1. olegchir
      31.08.2017 13:02

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


  1. AlexeyYM
    31.08.2017 22:46

    Давно хотел с MVC ознакомиться, спасибо:)


  1. avtokot84
    31.08.2017 22:46

    Ну мне как новичку, любая инфа на вес золота. Так что «респект» и «уважуха» хозяину этого доклада!


    1. Lure_of_Chaos
      01.09.2017 08:34

      <цинизм>Ценность Интернета как раз и состоит во множественном клонировании информации</цинизм>