Spring MVC Thymeleaf

Сегодя я покажу как создать веб приложение с помощью Spring MVC и серверного Java движка Thymeleaf.

Самое первое с чего начинают учить спринг это написание Spring MVC контроллер класса и отображение html страницы (которая на самом деле не совсем html) с приветствием. В этой статье мы структурируем этот момент и копнем немного глубже: подключим статические ресурсы и немного поработаем с отображением атрибутов на странице.

Если Вы работали с сервлетами и уже писали веб приложения на «чистой» java, без использования фреймворков — то понять этот материал Вам будет значительно проще. Для тех же, кто этого не сделал, я советую хотя бы ознакомиться с теорией. Так как Spring MVC базируется на понятии сервлета и всего что с ним связано.

Если говорить кратко то Spring MVC — это просто удобный способ принимать http запросы и управлять контентом.

Я долго думал как описать Thymeleaf для новичков, чтобы было проще некуда. Перелопатил кучу статтей. Но там почти везде копипаста с официального сайта.

В общем: Thymeleaf это библиотека, которая позволяет хранить html шаблоны на стороне сервера и выдавать их по вызову определенного кода.

Если говорить еще проще, то когда пользователь сидит в Интернете — он скорее всего сидит там через браузер (Хром, Мозила, Опера). То что ему показывается — это html страница, которую он запросил, когда ввел в поисковую строку запрос. Все, что находится на странице, оформленно в виде файла, который имеет расширение html и который состоит из html тегов. Вот такой файл и позволяет хранить на сервере Thymeleaf движок. Он не просто позволяет его хранить там, но также имеет функционал для управления контентом этого файла.

Когда Вы сейчас смотрите на статью, то видите текст, меню, возможно рекламу, кнопки и т.д. Статья — это наполнение страницы. Каждая такая страница со статьей имеет одинаковый вид. Все страницы моего сайта отличаются только наполнением. Внешний вид называют шаблоном. Тоесть все что без текста — это шаблон. Он один по всему сайту. Статьи находятся в базе данных, и когда Вы переходите по определенному урл, сервер смотрит какой контент Вы запрашиваете (по значению урл), берет шаблон и помещает в него текст статьи.

Thymeleaf вместе со Spring MVC позволяют управлять шаблонами веб страниц и наполнением этих самых страниц самым разным контентом и в самый разный способ. В зависимости от запросов польозвателей.

Сейчас я на примере покажу как осуществить то что описано выше.

Первое что я делаю когда хочу создать Spring приложение — иду на spring initializr. Это официальный сайт, который позволяет быстро сгенерировать костяк Spring Boot приложения с необходимыми зависимостями Maven или Gradle. Почему именно Spring Boot? Потому что сейчас это, если можно так сказать, более современная версия спринг фреймворка, которая включает в себя автонастройку. Она избавляет Вас от необходимости писать сложные классы настроек, которые нужно было писать раньше. Более подробно об этом можно почитать в статье Spring: обзор фреймворка для самых маленьких.

Для того чтобы создать приложение используя Spring MVC и Thymeleaf я выбираю соответствующие зависимости:

После этого я просто открываю проект как мавен проект в моем редакторе кода (Intellij idea) и начинаю кодить. Приложение, в котором Вы программируете не имеет значения. Я повторяю это практически в каждой статье. Этот пример можно однаково реализовать как в Intellij idea так и в Eclipse, NetBeans и т.д.

Вот примерная структура моего проекта:

структура Spring Boot MVC проекта
структура Spring Boot MVC проекта

В папке resources находятся директории: static, teamplates. application.properties файл сегодня нас не интересует. Мы не будем добавлять своих настроект в проект.

Папки static, teamplates находятся в нашем проекте не просто так. В Spring Boot автоконфигурации для веб, эти папки являются частью пути к ресурсам: ститическим (css, js, картинки) и шаблонам соответственно.

Первое что можно сделать — создать страницу приветствия или как еще ее иногда называют главную страницу.

Для удобства программистов комманда Spring сделала специальную настройку для главной страницы. Достаточно назвать ее index.html и поместить ее в папку static.

Для примера я добавлю сообщение с приветствием. Вы можете добавить любой шаблон, который пожелаете.

По умолчанию, встроенный в Spring Boot сервер Tomcat стартует с портом 8080. После запуска приложения и перехода в браузере по запросу http://localhost:8080 Вы должны увидеть главную страницу вашего проекта:

Вот так, очень просто и без лишних классов настроек мы смогли добавить страницу приветствия в наше приложение.

Чтобы добавить еще шаблонов по запросу или вывести контент, нам нужно прибегнуть к помощи Thymeleaf и Spring контроллера.

Контроллер — это простой джава класс, который помечен аннотацией @Controller. Этим Вы указываете спринг библиотеке, что данный класс будет принимать входящие http запросы и выдавать соответствующий контент.

За прием запросов будут отвечать методы данного класса, которые помечены соответствующей аннотацией. Как Вы помните существует несколько типов http запросов: GET, POST, PUT, DELETE и т.д. Если Вы этого не знаете — я настоятельно рекомендую почитать немного материала на эту тему. Вот как пример https://www.w3schools.com/tags/ref_httpmethods.asp

Для того, чтобы показать что метод будет принимать определенный запрос из вышеперечисленных, нужно поменить его одной из аннотаций @GetMapping, @PostMapping, @PutMapping и т.д. Один метод будет отвечать за один запрос. Внутрь аннотации нужно добавить атрибут value — часть урл пути запроса.

Например http://localhost:8080/greeting. Чтобы создать метод, который будет принимать такой гет запрос нужно навешать на него аннотацию @GetMapping(«/greeting») или @GetMapping(value = «/greeting»).

Spring контроллер обрабатывает запрос и выдает шаблон или делает редирект на другой запрос. Чтобы выдать шаблон, его сначала нужно создать. В папку templates нужно поместить html файл. Имя этого файла и будет возвращаться в качестве строки контроллер метода. Спринг возьмет имя файла, перейдет в папку templates и попытается найти шаблон по имени.

Движок Thymeleaf возьмет этот html, распарсит его и выдаст на клиент (браузер). Если в файле есть thymeleaf атрибуты — они будут распарсены и отформатированы в соответствии с синтаксисом таймлиф. Об этом немного позже.

Сейчас попытаемся просто выдать страницу по запросу.

Первое: создаем пакет а потом класс: controller, HomeController соответственно. Навешиваем аннотацию @Controller на класс. Создаем метод home(), который пока ничего не принимает на вход и возвращает строку «home_page«. Навешиваем на него аннотацию GetMapping(«/home»).

Переходим в папку templates и создаем файл home_page.html. В содержимое файла можно пока просто поместить приветствие.

Перезапускаем приложение и набираем в браузере http://localhost:8080/home

В результате, мы должны увидеть нашу страницу home_page.

Теперь, попытаемся создать кнопку с формой на главной странице, при нажатии на которую, можно перейти на home страницу и передать в ее содержимое параметр.

Переходим в index.html и создаем простою html форму которая будет вести на /home запрос:

Урл, куда будет вести форма мы указали в атрибуте action. Метод, мы указали в method атрибуте формы. Дальше мы указали input — поле для ввода текста. Это поле имеет имя login. Это очень важно. Так как именно по имени мы будем вынимать параметр в контроллере.

Дальше, мы переходим в HomeController и в методе home() добавляем входной параметр @RequestParam. Это аннотация, которая позволяет извлекать параметр из урл строки. Они принимает name, required атрибуты, которые являются не обязательными. Можно просто назвать имя переменной как и имя ввода формы и тогда параметр name можно не писать. Если Вы ожидаете что этот параметр будет обязательным, то атрибут required тоже можно не писать. Так как он по умолчанию false.

Теперь, когда Вы получили атрибут, с ним можно проводить всевозможные действия (изменить, записать в базу, передать в другой метод и тд). В нашем примере мы просто перешлем его обратно на страницу home_page.

Сделать это нам поможет Model — класс фреймворка спринг, который создан для манипулирования атрибутами. В результате манипулирования с классом Model и его методами мы получили следующий код:

package com.javamaster.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class HomeController {

    @GetMapping("/home")
    public String home(@RequestParam(required = false) String login, Model model) {
        System.out.println("login is: " + login);
        model.addAttribute("login", login);
        return "home_page";
    }
}

model.addAttribute принимает на вход ключ атрибута и его значение. По аналогии с Map. Таким образом мы приняли атрибут с запроса и передали его на страницу home_page. Теперь осталось только отобразить атрибут на странице с помощью thymeleaf.

Для этого нам нужно в теге html указать xmlns:th=»http://www.thymeleaf.org». Это атрибут пространства имен, который укажет что на этой странице мы используем thymeleaf.

В нужном нам месте отображения login атрибута нужно создать html элемент (любой) и добавить в него th:text=»${login}». С помощью этой конструкции, thymeleaf движок сможет найти атрибут по имени и отобразить его. В результате на странице в браузере, мы увидим только значение login, которое пришло с контроллера.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Home page</title>
</head>
<body>
<h2>Welcome <span th:text="${login}"></span></h2>
</body>
</html>

Вышеописанным образом можно передавать и отображать все что угодно: текст, цифры, объекты джава (будет позже) и тд. Таймлиф позволяет нам связать фронтенд с бекендом посредством тесной связи html и java класса.

Еще один важный пункт, который я хочу показать Вам — подключение статических ресурсов: стили CSS, код JS, картинки и тд. Без статических ресурсов создать полноценные веб проект будет не просто.

Первое, что нужно чтобы подключить ресурсы — поместить их в static директорию. Я советую создавать отдельные папки для разных типов статических ресурсов.

Дальше нужно на странице указать ссылку в соответствии с канонами html. Напоминаю, что для CSS это:

<link href="путь_к_CSS" rel="stylesheet">

для JS:

<script src="путь_к_JS"></script>

для изображения:

<img href="путь_к_изображению"/>

Для примера я создал 2 файла и поместил в них изменения свойства цвета текста и вывод в консоль браузера сообщения:

CSS файл
JS файл

Дальше, я подключил их вышеописанным способом к home_page.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Home page</title>
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<h2>Welcome <span th:text="${login}"></span></h2>
<script src="js/script.js"></script>
</body>
</html>

После перезапуска приложения и перехода на домашнюю страницу я получил результат:

результат подключения стилей и скриптов на Thymeleaf страницу
результат подключения стилей и скриптов на Thymeleaf страницу

На данный момент мы научились:

  • принимать параметры со страницы в Java контроллер класс;
  • передавать один параметр из класса джава на html страницу и отображать его с помощью средств Thymeleaf;
  • подлючать статические ресурсы к веб страницам.

Последнее, что я хочу продемонстрировать — передавать список java объектов на фронтенд и отображать его с помощью технологии Thymeleaf.

Мы будем передавать массив, который состоит из простого объекта с 2 полями. Я специально сделал только два поля чтобы не усложнять пример. Когда Вы увидите как это делать — то сможете передавать и отображать объекты любой сложности.

Я создаю простой джава класс с именем Order. Добавляю ему 2 поля, а также геттеры, сеттеры, equals, hashCode, toString. В результате я получил вот такой код:

package com.javamaster.model;

import java.util.Objects;

public class Order {

    private String title;
    private Integer price;

    public Order() {
    }

    public Order(String title, Integer price) {
        this.title = title;
        this.price = price;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Order order = (Order) o;
        return Objects.equals(title, order.title) &&
                Objects.equals(price, order.price);
    }

    @Override
    public int hashCode() {
        return Objects.hash(title, price);
    }

    @Override
    public String toString() {
        return "Order{" +
                "title='" + title + '\'' +
                ", price=" + price +
                '}';
    }
}

Дальше, я создаю отдельный контроллер класс чтобы передавать список ордеров на страницу.

В идеале, список заказов нужно хранить в базе данных, и Вы по желанию можете доделать этот туториал и подлючить базу данных. Я просто создам несколько тестовых объектов заказов и положу их в HashSet.

Дальше, я создаю метод контроллера, который по GET запросу /orders будет возвращать orders_page и атрибут со списком заказов:

package com.javamaster.controller;

import com.javamaster.model.Order;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.HashSet;
import java.util.Set;

@Controller
public class OrderController {

    private static Set<Order> orders = new HashSet<>();

    static {
        orders.add(new Order("iphone", 123));
        orders.add(new Order("book", 321));
        orders.add(new Order("chair", 567));
    }

    @GetMapping("/orders")
    public String orders(Model model) {
        model.addAttribute("orders", orders);
        return "orders_page";
    }

После этого, я уже по знакомой Вам схеме, создаю orders_page.html в папке templates и помещаю в него html код для простой таблицы, который я нашел в Интернете. Дальше, я для обхода массива из объектов использую Thymeleaf конструкцию th:block th:each.

Суть ее работы заключается в следующем. Мы создаем тег <th:block></th:block>. В атрибуты тега помещаем конструкцию th:each=»order : ${orders}» где orders это имя нашего атрибута который содержит массив заказов. order — это просто имя переменной которое мы выбрали при обходе заказов. Оно может быть любое в данном случае. По аналогии с java это

for (Object item : someList) {
    System.out.println(item);
}

Только здесь мы используем Thymeleaf синтаксис. Внутри тега <th:block> нам доступна переменная order и мы можем с ней работать уже как с простым атрибутом. В результате получился такой код:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Orders</title>
    <link href="css/order_style.css" rel="stylesheet">
</head>
<body>
<table>
    <tr>
        <th>Title</th>
        <th>Price</th>
    </tr>
    <th:block th:each="order : ${orders}">
        <tr>
            <td th:text="${order.title}"></td>
            <td th:text="${order.price}"></td>
        </tr>
    </th:block>
</table>
</body>
</html>

Стили order_style.css:

table {
    font-family: arial, sans-serif;
    border-collapse: collapse;
    width: 100%;
}

td, th {
    border: 1px solid #dddddd;
    text-align: left;
    padding: 8px;
}

tr:nth-child(even) {
    background-color: #dddddd;
}

После перезапуска приложения и перехода на http://localhost:8080/orders я вижу что мой код работает отлично:

Для большей динамичности и полноты примера я также создам метод для создания объекта Order. Первое, что нужно сделать — добавить форму на страницу заказов. Теперь в форме будет 2 поля: название, цена. На этот раз форма будет отправлять POST запрос на урл /createOrder. Воспользуемся еще одним методом таймлиф th:object=»${пустой_объект_который_будем_заполнять}». Его нужно поместить как атрибут тега формы. Вместе с action, method. В результате наша страница приобрела вид:


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Orders</title>
    <link ,href="css/order_style.css" rel="stylesheet">
</head>
<body>
<table>
    <tr>
        <th>Title</th>
        <th>Price</th>
    </tr>
    <th:block th:each="order : ${orders}">
        <tr>
            <td th:text="${order.title}"></td>
            <td th:text="${order.price}"></td>
        </tr>
    </th:block>
</table>
<form action="/createOrder" method="post" th:object="${order}">
    <input name="title" type="text">
    <input name="price" type="text">
    <input type="submit" value="Create order">
</form>
</body>
</html>

Просто так добавить th:object=»${order}» не получится. order нужно получить с контроллера. Причем этот атрибут нужно получать во время отображения страницы. За отображение страницы заказов у нас отвечает метод orders в классе OrderController. Нужно в этом методе добавить код для отправки пустого объета Order на страницу: model.addAttribute(«order», new Order()); Вот теперь код заработает.

Дальше, нужно написать код для приема запроса /createOrder и сохранения заказа в коллекции. Вы можете переделать чтобы заказ сохранялся в базе.

@PostMapping("/createOrder")
    public String createOrder(@ModelAttribute Order order) {
        System.out.println("order is : " + order);
        manageOrders(order);
        return "redirect:/orders";
    }

Не забывайте, что создание заказов это POST запрос. Поэтому аннотация будет @PostMapping. Для приема атрибута мы используем аннотацию @ModelAttribute которая идет с библиотекой Spring Web, использование которой мы сейчас изучаем. Метод manageOrders это простой приватный метод, куда я вынес код по добавлению заказов в список:

private static Set<Order> manageOrders(Order order) {
        if (order != null) orders.add(order);
        return orders;
    }

Ну и самое интересное. После обработки запроса мы можем не только отображать страницы, но и перенаправлять запрос на другой урл. Как видите, здесь используется ключевое слово redirect:. После, указывается путь, по которому мы хотим перенаправить пользователя. В данном случае, я перенаправляю пользователя на /orders запрос. После создания заказа, сработает запрос /orders и программа зайдет в метод orders. В нем мы заново отправим все заказы из списка и заново их отобразим на странице orders_page. Только в этот раз заказов будет на один больше, так как в коллекции уже будет созданный заказ.

Это все, что я хотел показать в рамках этого туториала. Теперь Вы сможете запрограммировать веб приложение на Java без лишней возни и заморочек с настройками.

Весь код примера находится на гитхаб: https://github.com/caligula95/spring-mvc-example

Я также записал видео по этому гайду:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *