В даному матеріали ми розберемо основи Spring MVC і Thymeleaf. Спробуємо написати простий веб додаток на Java за допомогою Spring MVC і Thymeleaf.
Статтю буде легше сприймати після розуміння того, як працює мережа, що таке http протокол, java servlet та MVC підхід. Тому переконайтесь, що маєте розуміння описаних вище тем. Посилання на весь код буде в кінці матеріалу. Також там буде відео англійською, яке можна подивитись, якщо не дуже подобається читати. Я раджу спочатку прочитати матеріал, а потім подивитись відео.
Що таке Spring MVC
Spring MVC (Model View Controller) – компонент Spring Framework. Є величезний фреймворк, який називається Spring. Він має багато компонентів і бібліотек, які спрощують програмістам написання додатків на Java. Spring MVC це один з його компонентів. Він дозволяє писати веб програми використовуючи вже відомий нам MVC підхід.
Робота Spring MVC базується на Java сервлетах. Замість обʼємного сервлету і його методів, даний фреймворк пропонує зручні анотації і стислий код. Все для того, щоб розробники фокусувались на бізнес логіці, замість складних конфігурацій в коді.
Spring MVC підтримує багато технологій відображення сторінки: JSP, PDF, HTML, Thymeleaf і тд. Наш сьогоднішній приклад буде на Thymeleaf.
Що таке Thymeleaf
Thymeleaf (читається як таймліф) – сучасний серверний механізм Java-шаблонів для веб і автономних середовищ, здатний обробляти HTML, XML, JavaScript, CSS. Це така собі більш сучасна версія JSP. Ми будемо вбудовувати результати Java коду в HTML сторінку.
Основні компоненти Spring MVC
Пропоную не затягувати з теорією, а приступити відразу до практики. Заодно розглянемо основні компоненти Spring MVC на прикладі нашого веб додатку.
Перш за, все нам потрібен Maven web проєкт. До нього додаємо залежності для spring-mvc і thymeleaf. Є ресурс Spring Initializr, який був спеціально розроблений командою Spring Framework для генерації шаблонів додатків. Це веб сторінка, де можна швидко і зручно створити готовий java проєкт з потрібними залежностями Spring.
Обираєте необхідні для себе параметри, заповнюєте поля для назви проєкту, артифакту, опису. Додаєте потрібні залежності і натискаєте кнопку Generate. Готовий проєкт завантажиться на компʼютер у вигляді архіву. Залишилось тільки розпакувати архів, відкрити проєкт в IDEE і почати кодити.

Існує маса інших способів створювати мавен проєкти, але це мій улюблений.
В результаті всіх вищеописаних маніпуляцій, мій проєкт наразі має такий вигляд:

Зверніть увагу на файл pom.xml. В ньому є залежності spring-boot-starter-thymeleaf і spring-boot-starter-web. Це якраз ті залежності, що потрібні для Spring MVC.
Там також є lombok залежність. Однак, якщо не дуже знайомі з цією бібліотекою, то на неї можна не зважати. Вона тут для генерації коду для getter, setter, equals, hashCode, toString, конструкторів.
Controller – основа Spring MVC
Контроллер це клас, який помічено анотацією @Controller. Як тільки ви навішали дану анотацію на клас, він стає контроллером. Якщо ви згадаєте теорію по MVC, то зрозумієте, що призначення даного класу – отримання і передача даних від клієнта (View) до моделі (Model). У випадку Spring MVC, контроллер також відповідальний за відображення певних сторінок на відповідний запит. Controller дуже схожий на Java сервлет як за функціоналом, так і за зоною відповідальності. Пропоную відразу поглянути на код мого контроллера:
package com.springmvcapp.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/")
public class HomeController {
@GetMapping
public String getHomePage() {
return "home_page";
}
}
Щоб обслуговувати HTTP запити за певним URL, можна скористатись анотацією @RequestMapping. Це опціональна анотація. Замість неї адресу можна вказати як параметр value в @GetMapping анотацію: @GetMapping(value = “/”).
Метод, який ви хочете призначити відповідальним за певний урл потрібно обовʼязково позначити анотацією накшталт @GetMapping, @PostMapping. Як ви могли здогадатись, префікс залежить від того, який HTTP метод буде обслуговувати ваша функція.
Метод повинен повертати String, де значення це назва thymeleaf сторінки. Якщо ви будете користуватись стандартним розміщенням директорій, які створив Spring Boot, то thymeleaf сторінки потрібно створювати в папці resources/templates. Звісно, завжди можна написати власні конфігурації і змінити стандартні налаштування Spring Boot за потреби. Цього ми робити в даному матеріалі не будемо.
В папці resources/templates створюємо звичайну HTML сторінку. Дайте їй назву home_page. Таким чином, коли наш метод getHomePage буде обслуговувати запит за адресою localhost:8080/, він знайде дану HTML і відобразить її користувачеві:

Стандартна index.html
Доки ми не перейшли до складніших понять, хотів би ще описати поведінку index.html сторінки. Справа в тому, що ми можемо створити index.html сторінку в папці resources/static і вона буде відображатись за абсолютною адресою навіть без контроллера. Дану можливість нам надають стандартні налаштування Spring Boot. Користуватись чи ні вирішуйте на власний розсуд. Головне, щоб ви це знали і розуміли як сторінка відображається без контроллера.
Коли HTML стає Thymeleaf
Можливо ви помітили, що ми створили звичайну HTML сторінку в папці templates і наш контроллер її чудово відобразив. Хоча ми наче як збирались повертати Thymeleaf. Вся справа в тому, що це і є таймліф сторінка. Для нас, як розробників, це HTML синтаксис, однак механізм Thymeleaf розуміє HTML. Шаблони HTML, написані в Thymeleaf, все ще виглядають і працюють як HTML, дозволяючи фактичним шаблонам, які запускаються у вашій програмі, продовжувати працювати як корисні артефакти дизайну.
Щоб мати змогу впроваджувати дані з Java коду в Thymeleaf сторінки, слід скористатись спеціальними таймліф атрибутами. Підключити їх можна безпосередньо на сторінці. Наприклад <html lang=”en” xmlns:th=”http://www.thymeleaf.org”> . Після даного підключення ми можемо використовувати th атрибути. Таймліф представляє дуже великий набір атрибутів і функцій, які не вдасться описати в даному матеріалі. Тут я продемонструю тільки найбільш вживані. Весь інший функціонал завжди можна знайти на офіційних джерелах. Тим більше, що бібліотека все ще оновлюється і активно розробляється.
Передача атрибутів з контролера на Thymeleaf
Щоб передати дані з контроллера на сторінку, можна скористатись стандартним Spring MVC класом Model, який знаходиться в пакеті org.springframework.ui.Model, використавши метод model.addAttribute(“імʼя атрибуту”, значення_атрибуту). На сторінці Thymeleaf можна побачити цей атрибут, звернувшись за його імʼям за таким синтаксисом: “${імʼя_атрибуту}”. Значенням можуть бути як прості дані в вигляді чисел чи рядків, так і складні Java обʼєкти, включаючи масиви і списки. Ось приклад, де ми передаємо на сторінку book_page відразу 2 атрибути: userBooks і userLogin.
@GetMapping
public String getBookPage(@RequestParam(required = false, name = "login") String login,
@RequestParam(required = false) String email,
Model model) {
model.addAttribute("userLogin", login);
List<BookModel> books = bookService.getAllBooksByLogin(userLogin);
model.addAttribute("userBooks", books);
return "book_page";
}
В прикладі вище, ми також приймаємо параметри запиту з URL, використовуючи @RequestParam анотацію. Дана анотація може приймати різні значення: чи обовʼязковий цей параметр, імʼя параметру. Якщо імʼя змінної співпадає з іменем атрибуту, тоді вказувати name не обовʼязково. Spring зможе сам підставити параметри запиту в змінну.
Далі, ми відображаємо атрибути на сторінці:
<h1>Welcome to the book page <span th:text="${userLogin}"></span></h1>
<table>
<tr>
<th>Title</th>
<th>Year</th>
<th></th>
<th></th>
</tr>
<th:block th:each="book : ${userBooks}">
<tr>
<td th:text="${book.title}"></td>
<td th:text="${book.year}"></td>
<td><button><a th:href="@{|/books/edit/${book.title}|}">Edit</a></button></td>
<td><button><a th:href="@{|/books/delete/${book.title}|}">Delete</a></button></td>
</tr>
</th:block>
</table>
Використовуючи th:text=”${userLogin}”, ми можемо відобразити текстовий атрибут. Для відображення обʼєктів, слід скористатись більш складним синтаксисом.
<th:block th:each="book : ${userBooks}">
<tr>
<td th:text="${book.title}"></td>
<td th:text="${book.year}"></td>
<td><button><a th:href="@{|/books/edit/${book.title}|}">Edit</a></button></td>
<td><button><a th:href="@{|/books/delete/${book.title}|}">Delete</a></button></td>
</tr>
</th:block>
Ми отримали userBooks як список і починаємо обходити його через конструкцію th:each. Її синтаксис видно вище. Він чимось нагадує for-each від Java. Далі, ми можемо отримати один обʼєкт в циклі і звертаємось до обʼєкта за іменем його полів. Навіть якщо вони приватні. Наш клас BookModel має наступний вигляд:
public class BookModel {
private String title;
private Integer year;
}
Передача даних з Thymeleaf на контроллер.
Зверніть увагу на <button><a th:href=”@{|/books/edit/${book.title}|}”>Edit</a></button>. Ми використовуємо th:href щоб створити данамічне посилання, яке буде містити в кінці назву книги. Це посилання ми використовуємо для відкриття сторінки редагування обʼєкта.
Щоб створити новий обʼєкт, слід скористатись HTML формою з невеликим вкрапленням thymeleaf.
Код контроллера для відкриття форми створення книги і прийняття запиту на створення:
@GetMapping("/create")
public String getCreateBookPage(Model model) {
model.addAttribute("newBook", new BookModel());
return "create_book_page";
}
@PostMapping("/createBook")
public String createBook(@ModelAttribute BookModel book) {
bookService.save(book);
return "redirect:/books";
}
Ми попередньо створили create_book_page сторінку з формою. Потім передаємо на неї атрибут newBook. Це обʼєкт з пустими полями. Оскільки ми передали тільки new BookModel(). Далі ми використаємо цей атрибут на формі сторінки:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post" action="/books/createBook" th:object="${newBook}">
<input type="text" name="title", placeholder="Title">
<input type="number" name="year", placeholder="Year">
<input type="submit" value="Save">
</form>
</body>
</html>
Ми використовуємо th:object=”${newBook}” щоб зʼєдани дану форму з книгою. Імена інпутів повинні співпадати з іменами полів класу BookModel. Thymeleaf зуміє перенести значення полів з форми до Java обʼєкту newBook.
В методі createBook, ми приймаємо відразу цілий обʼєкт BookModel за допомогою анотації @ModelAttribute. Потім ми можемо використовувати дані на власний розсуд. Зверніть увагу, я не повертаю сторінку в даному методі. Натомість, я роблю редірект на сторінку /books. Таким чином, використовуючи return вираз, можна не тільки відображати сторінки, а й перенаправляти користувача за певною адресою.
Я не буду демонструвати тут приклад редагування і видалення обʼєктів, оскільки принцип там дуже схожий зі створенням. Все це буде на відео і в коді. Зрозумівши ідею, ви зможете створювати і більш складні маніпуляції з обʼєктами.
Підключення статичних ресурсів на Thymeleaf
Для повноцінного веб додатку, вам потрібно знати, як підключити статичні ресурси на сторінку. Адже, наразі складно уявити собі сучасні веб сайти без CSS, JS і мультимедіа.
Синтаксис для статичних ресурсів не відрізняється від стандартного HTML синтаксису. Як ви вже знаєте, щоб підключити стилі CSS потрібно прописати <link rel=”stylesheet” href=”посилання”> в тег header. Однак, ми маємо справу не просто з HTML. Це все таки Thymeleaf і Java. Велике значення буде розміщення і посилання на ці ресурси.
Перш за все, потрібно поміситити всі статичні ресурси в папку static. Це стандартні конфігурації Spring. Тільки тоді сервер зможе обслуговувати їх як статичні ресурси. Далі, слід вказувати посилання без static. Ось приклад підключення CSS: <link rel=”stylesheet” href=”styles/style.css”>. Файл style.css знаходиться в папці resources/static/styles. Такий принцип стосується усіх статичних ресурсів.
Використання Thymeleaf фрагментів.
Інколи, нам потрібно використати один і той самий фрагмент HTML коду на різних сторінках. Як приклад, навігаційне меню.
Thymeleaf має підтримку фрагментів. Він надає змогу виділити окремий код як фрагмент, перемістити його в окремий файл, і потім використовувати за потреби.
Спочатку потрібно створити фрагмент використовуючи th:fragment=”імʼя_фрагменту”:
<div class="topnav" th:fragment="navigation"> <a class="active" href="/">Home</a> <a href="/books">Books</a> </div>
Далі, слід підключити цей код в потрібному місці html сторінки:
<div th:insert="~{parts/navigation::navigation}"></div>
В прикладі вище, parts/navigation це шлях до нашого файлу з фрагментом, navigation – імʼя самого фрагменту.
Це не єдиний спосіб уникнути дублікації коду в Thymeleaf. Як я вже згадав раніше, таймліф і Spring MVC це дуже великі бібліотеки. Весь їх функціонал не вдасться вмістити в одну статтю. Моїм завданням було показати вам основи Spring MVC і Thymeleaf, щоб дати змогу приступити до написання власних веб додатків за допомогою Spring. А вже під час практити, ви зможете познайомитись з величезним функціоналом даного фреймворку і по справжньому оцінити: наскільки він спрощує програмування.
Посилання на код: https://github.com/caligula95/spring-mvc-application

Залишити відповідь