16. Вступ до Spring MVC
16.1. Роль Spring MVC у веб-додатку
Розглянемо роль Spring MVC у розробці веб-додатку. Найчастіше такий додаток будується на багатошаровій архітектурі, наприклад, такій:
![]() |
- шар [Web] — це шар, що взаємодіє з користувачем веб-додатка. Користувач взаємодіє з веб-додатком через веб-сторінки, що відображаються у браузері. Саме в цьому шарі, і лише в ньому, розташований Spring MVC;
- шар [métier] реалізує правила управління додатком, такі як розрахунок заробітної плати або рахунку-фактури. Цей рівень використовує дані, що надходять від користувача через рівень [Web], а також дані з SGBD через рівень [DAO];
- Рівень [DAO] (об’єкти доступу до даних), рівень [ORM] (об’єктно-реляційний мапер) та драйвер JDBC забезпечують доступ до даних SGBD. Рівень [ORM] забезпечує зв’язок між об’єктами, що обробляються рівнем [DAO], та рядками й стовпцями таблиць реляційної бази даних. Специфікація під назвою JPA (Java Persistence API) дозволяє абстрагуватися від використовуваного ORM, якщо він реалізує ці специфікації. Саме так буде в цьому посібнику, тому відтепер ми будемо називати шар ORM шаром JPA;
- інтеграція шарів здійснюється фреймворком Spring;
16.2. Модель розробки Spring MVC
Spring MVC реалізує так звану архітектурну модель MVC (Модель – Представлення – Контролер) наступним чином:
![]() |
Обробка запиту клієнта відбувається наступним чином:
- запит — запитувані URL мають вигляд http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&.... [Front Controller] використовує файл конфігурації або анотації Java для «маршрутизації» запиту до відповідного контролера та відповідної дії в межах цього контролера. Для цього він використовує поле [Action] з URL. Решта URL [/param1/param2/...] складається з необов’язкових параметрів, які будуть передані до дії. Значенням параметра C у MVC тут є рядок [Front Controller, Contrôleur, Action]. Якщо жоден контролер не може обробити запитувану дію, веб-сервер відповість, що запитуваний URL не знайдено.
- Обробка
- (продовження)
- обрана дія може використовувати параметри parami, які їй передала [Front Controller]. Вони можуть походити з кількох джерел:
- з шляху [/param1/param2/...] файлу URL,
- з параметрів [p1=v1&p2=v2] від URL,
- з параметрів, надісланих браузером разом із запитом;
- під час обробки запиту користувача дії може знадобитися рівень [métier] [2b]. Після обробки запиту клієнта це може викликати різні відповіді. Класичним прикладом є:
- сторінка з повідомленням про помилку, якщо запит не вдалося обробити належним чином
- сторінка підтвердження в іншому випадку
- дія вимагає відображення певного виду [3]. Цей вид відображатиме дані, які називаються моделлю виду. Це «M» у MVC. Дія створить цю модель M [2c] і вимагатиме відображення певного виду V [3];
- відповідь — обраний вигляд V використовує модель M, створену дією, для ініціалізації динамічних частин відповіді HTML, яку він повинен надіслати клієнту, а потім надсилає цю відповідь.
Для веб-сервісу / jSON попередня архітектура дещо змінена:
![]() |
- у [4a] модель, яка є класом Java, перетворюється на рядок jSON за допомогою бібліотеки jSON;
- у [4b] цей рядок jSON надсилається до браузера;
Тепер уточнимо зв’язок між веб-архітектурою MVC та багаторівневою архітектурою. Залежно від того, як ми визначаємо модель, ці два поняття можуть бути пов’язані або ні. Розглянемо однорівневий веб-додаток Spring MVC:
![]() |
Якщо ми реалізуємо шар [Web] за допомогою Spring MVC, ми отримаємо веб-архітектуру MVC, але не багатошарову архітектуру. У цьому випадку шар [web] буде відповідати за все: представлення, бізнес-логіку, доступ до даних. Цю роботу виконуватимуть дії.
Тепер розглянемо багаторівневу веб-архітектуру:
![]() |
Рівень [Web] можна реалізувати без фреймворку та без дотримання моделі MVC. У цьому випадку ми дійсно маємо багаторівневу архітектуру, але веб-рівень не реалізує модель MVC.
Наприклад, у середовищі .NET цей веб-шар [Web]може бути реалізований за допомогою ASP.NET та MVC, і тоді ми отримуємо багатошарову архітектуру з шаром [Web] типу MVC. Зробивши це, можна замінити цей шар ASP.NET MVC на класичний шар ASP.NET (WebForms), зберігаючи решту (бізнес-логіка, DAO, ORM) без змін. У результаті ми отримуємо багаторівневу архітектуру з рівнем [Web], який більше не є типом MVC.
У MVC ми зазначили, що модель M відповідає представленню V, c.a.d, — сукупності даних, що відображаються представленням V. Наводимо ще одне визначення моделі M для MVC:
![]() |
Багато авторів вважають, що те, що знаходиться праворуч від шару [Web], утворює модель M для MVC. Щоб уникнути двозначностей, можна говорити про:
- моделі домену, коли мається на увазі все, що знаходиться праворуч від шару [Web]
- про модель подання, коли йдеться про дані, що відображаються поданням V
Надалі термін «модель M» позначатиме виключно модель подання V.
16.3. Веб-проект / jSON із Spring MVC
Сайт [http://spring.io/guides] пропонує навчальні посібники для початківців, що допоможуть ознайомитися з екосистемою Spring. Ми скористаємося одним із них, щоб дізнатися про конфігурацію Maven, необхідну для проекту Spring MVC.
16.3.1. Демонстраційний проект
![]() |
- у [1] ми імпортуємо один із посібників Spring;
![]() |
- у [2] ми обираємо приклад [Rest Service];
- у [3] вибираємо проект Maven;
- у [4] вибираємо остаточну версію посібника;
- у [5] підтверджуємо;
- у [6] — імпортований проєкт;
Веб-сервіси, доступні через стандартні URL і які надають текст jSON, часто називають сервісами REST (REpresentational State Transfer). Сервіс вважається Restful, якщо він дотримується певних правил.
Тепер розглянемо імпортований проєкт, спочатку його конфігурацію Maven.
16.3.2. Конфігурація Maven
Файл [pom.xml] має такий вигляд:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-rest-service</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<properties>
<start-class>hello.Application</start-class>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
</project>
- рядки 6–8: властивості проекту Maven. Відсутній тег [<packaging>], що вказує тип файлу, який створюється під час компіляції Maven. За його відсутності використовується тип [jar]. Отже, додаток є консольним виконуваним файлом, а не веб-додатком, для якого тип упаковки був би [war];
- рядки 10–14: проект Maven має батьківський проект [spring-boot-starter-parent]. Саме він визначає основну частину залежностей проекту. Їх може бути достатньо, і в цьому випадку додаткові залежності не додаються, або ж їх може не вистачати, і тоді додаються відсутні залежності;
- рядки 17–20: артефакт [spring-boot-starter-web] містить бібліотеки, необхідні для проекту Spring MVC типу веб-сервісу, у якому не генеруються візуальні елементи. Цей артефакт містить величезну кількість бібліотек, зокрема бібліотеки вбудованого сервера Tomcat. Саме на цьому сервері буде виконуватися додаток;
Бібліотек, що входять до складу цієї конфігурації, дуже багато:
![]() | ![]() |
Вище показано три архіви сервера Tomcat.
16.3.3. Архітектура сервісу Spring [web / jSON]
Для веб-сервісу / jSON Spring MVC реалізує модель MVC наступним чином:
![]() |
- у [4a] модель, яка є класом Java, перетворюється на рядок jSON за допомогою бібліотеки jSON;
- у [4b] цей рядок jSON надсилається до браузера;
16.3.4. Контролер C
![]() |
Імпортований додаток має такий контролер:
package hello;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
@RequestMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
- рядок 9: анотація [@RestController] перетворює клас [GreetingController] на контролер Spring, тобто його методи реєструються для обробки URL. Ми вже бачили подібну анотацію [@Controller]. Результатом методів цього контролера був тип [String], який являв собою ім’я поданого виду. Тут ситуація інша. Методи контролера типу [@RestController] повертають об’єкти, які серіалізуються для відправки до браузера. Тип серіалізації залежить від конфігурації Spring MVC. У цьому випадку вони будуть серіалізовані у формат jSON. Саме наявність бібліотеки jSON у залежностях проєкту змушує Spring Boot шляхом автоконфігурації налаштувати проєкт саме таким чином;
- рядок 14: анотація [@RequestMapping] вказує на URL, який обробляє метод, у даному випадку URL [/greeting];
- рядок 15: ми вже пояснювали анотацію [@RequestParam]. Результатом, що повертається методом, є об’єкт типу [Greeting].
- рядок 12: ціле число типу «long» атомарного типу. Це означає, що воно підтримує паралельний доступ. Кілька потоків можуть одночасно намагатися збільшити значення змінної [counter]. Це відбуватиметься коректно. Потік може зчитати значення лічильника лише тоді, коли потік, який його змінює, завершив свою операцію.
16.3.5. Модель M
Модель M, створена за допомогою попереднього методу, є таким об’єктом [Greeting]:
![]() |
package hello;
public class Greeting {
private final long id;
private final String content;
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}
}
Перетворення jSON цього об’єкта створить рядок {"id":n,"content":"текст"}. У підсумку рядок jSON, створений методом контролера, матиме такий вигляд:
або
16.3.6. Виконання
![]() |
Клас [Application.java] є виконуваним класом проекту. Його код такий:
package hello;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Ми вже зустрічали та пояснювали цей код у попередньому прикладі. Запустимо проект:
![]() |
Ми отримуємо такі записи консолі:
- рядок 13: сервер Tomcat запускається на порту 8080 (рядок 12);
- рядок 17: сервлет [DispatcherServlet] присутній;
- рядок 20: виявлено метод [GreetingController.greeting];
Щоб протестувати веб-додаток, надсилаємо запит на URL [http://localhost:8080/greeting]:
![]() | ![]() |
Ми успішно отримуємо очікуваний рядок jSON. Може бути цікаво подивитися заголовки HTTP, надіслані сервером. Для цього ми скористаємося розширенням для Chrome під назвою [Advanced Rest Client] (Chrome / Ctrl-T / Меню [Applications] / [Advanced Rest Client] — див. Додатки, параграф 23.11):
![]() |
- у [1] — запитуване URL;
- у [2] використовується метод GET;
- у [3] — відповідь jSON;
- у [4] сервер повідомив, що надсилає відповідь у форматі jSON;
- у [5] запитується той самий URL, але цього разу з POST;
- у [7] інформація надсилається на сервер у формі [urlencoded];
- у [6] — параметр name із його значенням;
- у [8] браузер повідомляє серверу, що надсилає йому інформацію [urlencoded];
- у [9] — відповідь jSON від сервера;
16.3.7. Створення виконуваного архіву
Тепер ми створюємо виконуваний архів:
![]() |
![]() |
- у [1]: виконується ціль Maven;
- у [2]: є дві цілі (goals): [clean] для видалення папки [target] з проекту Maven, [package] для її повторного створення;
- у [3]: згенерована папка [target] буде розміщена в цій папці;
- у [4]: генерується ціль;
У журналах, що з’являються в консолі, важливо побачити плагін [spring-boot-maven-plugin]. Саме він генерує виконуваний архів (див. [pom.xml] нижче):
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
У консолі перейдіть до створеної папки:
D:\Temp\wksSTS\gs-rest-service\target>dir
...
11/06/2014 15:30 <DIR> classes
11/06/2014 15:30 <DIR> generated-sources
11/06/2014 15:30 11 073 572 gs-rest-service-0.1.0.jar
11/06/2014 15:30 3 690 gs-rest-service-0.1.0.jar.original
11/06/2014 15:30 <DIR> maven-archiver
11/06/2014 15:30 <DIR> maven-status
...
- рядок 5: створений архів;
Цей архів запускається наступним чином:
D:\Temp\wksSTS\gs-rest-service-complete\target>java -jar gs-rest-service-0.1.0.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.1.0.RELEASE)
2014-06-11 15:32:47.088 INFO 4972 --- [ main] hello.Application
: Starting Application on Gportpers3 with PID 4972 (D:\Temp\wk
sSTS\gs-rest-service-complete\target\gs-rest-service-0.1.0.jar started by ST in
D:\Temp\wksSTS\gs-rest-service-complete\target)
...
Тепер, коли веб-додаток запущено, його можна відкрити у браузері:
![]() |
16.3.8. Розгортання додатка на сервері Tomcat
Як і в попередньому проєкті, ми змінюємо файл [pom.xml] наступним чином:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-rest-service</artifactId>
<version>0.1.0</version>
<packaging>war</packaging>
...
</project>
- рядок 9: потрібно вказати, що буде створено архів war (Web ARchive);
Крім того, потрібно налаштувати веб-додаток. За відсутності файлу [web.xml] це робиться за допомогою класу, що успадковує [SpringBootServletInitializer]:
![]() |
Клас [ApplicationInitializer] має такий вигляд:
package hello;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
public class ApplicationInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
- рядок 6: клас [ApplicationInitializer] успадковує клас [SpringBootServletInitializer];
- рядок 9: метод [configure] перевизначено (рядок 8);
- рядок 10: вказано клас, який налаштовує проект;
Щоб виконати проект, можна вчинити так:
![]() |
- у [1-2] запускаємо проект на одному із серверів, зареєстрованих у IDE Eclipse;
Після цього можна завантажити URL [http://localhost:8080/gs-rest-service/greeting/?name=Mitchell] у браузері:
![]() |
16.4. Conclusion
Ми запровадили тип проектів Spring MVC, у яких веб-додаток надсилає потік jSON до браузера. Тепер ми розробимо веб-додаток / jSON для оприлюднення в Інтернеті бази даних [dbproduitscategories], яку ми розглядали в попередніх розділах.























