Переменные пути Thymeleaf с загрузкой Spring

Вступление

Thymeleaf – механизм создания шаблонов, используемый многими разработчиками программного обеспечения Java в веб-приложениях на базе Spring. Важной особенностью любого веб-приложения является поддержка динамических URL-адресов и переменных пути внутри этих URL-адресов.

Большинство API-интерфейсов REST широко использует переменные пути для указания идентификаторов элементов, с которыми они выполняют операции. Типичный пример:

https://www.somewebsite.com/viewPost/path-variables-with-spring-boot
# OR
https://www.somewebsite.com/viewProduct/5

В обоих случаях мы пытаемся найти ресурс, обозначенный определенным идентификатором. В первом случае мы идентифицируем ресурс по заголовку path-variables-with-spring-boot, а во втором мы идентифицируем его с помощью идентификатора 5.

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

На протяжении всего урока мы будем использовать модель Post, запись в блоге, в которой есть идентификатор и какой-то контент:

public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "post_sequence")
    private Long id;
    private String content;

    // Constructors, Getters, Setters, and toString 
}

Поскольку мы используем загрузку Spring, давайте ещё и загрузим пострепозицию на основе JpaRepository, которая позволяет нам выполнять операции CRUD:

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {}

Переменные пути Thymeleaf с загрузкой Spring

Некоторые URL-адреса являются динамическими. Учитывая тот факт, что мы можем создавать, читать, обновлять и удалять объекты публикации, нам понадобятся динамические URL-адреса для запросов на ПОЛУЧЕНИЕ (GET), ОБНОВЛЕНИЕ (UPDATE) и УДАЛЕНИЕ (DELETE).

Начнём наш проект с нескольких постов:

@GetMapping("/initialize")
public ResponseEntity<String> initialize() {
    Post post1 = new Post("Content of post 1");
    Post post2 = new Post("Content of post 2");
    Post post3 = new Post("Content of post 3");
    postRepository.saveAll(List.of(post1, post2, post3));
    
    return ResponseEntity.ok("Initialized posts");
}

Как только мы достигнем конечной точки инициализации (/initialize), будут сгенерированы и сохранены в базе данных три поста. Теперь давайте определим статический URL-адрес для пользователя, чтобы получать все сообщения:

@GetMapping("/viewPosts")
public String viewAllPostsAndComments(Model model){
    List<Post> postList = postRepository.findAll();
    model.addAttribute("postList", postList);
    
    return "all-posts";
}

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

Поскольку было бы непрактично (и нереально) создавать обработчик запросов для каждой записи, мы можем создать динамический обработчик, который принимает любой идентификатор записи, находит запись в базе данных и выводит ее:

@GetMapping("/viewPost/{postId}")
public String viewPost(@PathVariable("postId") Long postId, Model model) {
    Post post = postRepository.findById(postId).get();
    model.addAttribute("post", post);
    
    return "view-post";
}

Так мы описали @GetMapping для URL/viewpost/{postID}. postId, заключенный в фигурные скобки, – это динамическая переменная, которая может присвоить любое значение и/или тип. В подписи нашего метода мы использовали аннотацию @PathVariable, установив имя переменной path и присвоив ей тип, на который мы можем ссылаться, – Long postID.

Как только запускается запрос GET, на конечной точке /viewpost/5, postId преобразуется в Long, и мы можем использовать его для поиска сообщения по его идентификатору в базе данных. Однако если мы передадим другой тип, например, String – /viewPost/some-post, контролёр выдаст исключение:

java.lang.NumberFormatException: For input string: "some-post"

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

<div th:each="post : ${postList}">
   <p th:text="${post.content}"></p>
   <a class="btn btn-info" th:href="@{/viewPost/{id}(id = ${post.id})}">View Post</a>
</div>

Здесь мы повторили каждое сообщение из postList (список сообщений, которые мы добавили в экземпляр модели), и для каждого из них добавили кнопку, позволяющую пользователю просматривать сообщение. Атрибут href приводит нас к:

th:href="@{/viewPost/{id}(id = ${post.id})}

Это стандартный синтаксис URL-адреса для добавления параметров в Thymeleaf. Стандартное выражение @{}, используемое для ссылок, также принимает переменные пути. {id} – это переменная пути, которую мы можем установить извне. Здесь мы указали на post.id. После визуализации на стороне сервера это выражение оценивается так:

<a href="localhost:8080/viewPost/1" class="btn btn-info" >View Post</a>

Нажав на кнопку, вы запустите конечную точку /viewpost/{postID}, при этом значение postId будет преобразовано в 1, а пострепозитарий получит сообщение с идентификатором 1, возвращая его к view-post:

<div class="container">
    <div class="row">
        <div class="col-md-12 bg-light">
            <p th:text="${post.content}"></p>
        </div>
    </div>
</div>

Заключение

Переменные пути являются ключевой особенностью API-интерфейсов REST. В этом руководстве мы рассмотрели, как использовать переменные пути в Spring Boot с помощью Thymeleaf.

Ответить