24 tháng 10 năm 2024 | Máy tính
Thymeleaf là một động cơ mẫu phổ biến trong Java, có khả năng xử lý HTML, XML, JavaScript, CSS và văn bản thuần túy. Động cơ này tích hợp mượt mà với Spring Boot và dễ dàng truy cập vào các lớp Model Java cùng các trường của nó, cho phép hiển thị nội dung động trên giao diện người dùng. Hơn nữa, Thymeleaf cung cấp một tập hợp các biểu thức đơn giản nhưng mạnh mẽ để hỗ trợ vòng lặp, điều kiện, các công cụ tĩnh và truy cập vào các Bean của Spring. Ngoài ra, Thymeleaf cũng hỗ trợ tốt cho việc mở rộng tùy chỉnh và tạo form.
Bài viết này sẽ hướng dẫn cách tích hợp Thymeleaf vào Spring Boot để xây dựng một ứng dụng Web đơn giản. Mục tiêu của chúng ta là tạo một trang web tổng hợp blog với bốn trang chính: bắn cá máy xèng online Trang chủ, Danh sách blog, Chi tiết blog và Nộp blog. Kết quả cuối cùng trông như sau:
!Ứng dụng tổng hợp blog
(Động ảnh trên thể hiện trình tự thao tác: Truy cập trang chủ, xem danh sách blog, xem chi tiết blog, nộp blog và chuyển đến trang danh sách)
Phiên bản JDK, Maven, Spring Boot và Thymeleaf được sử dụng trong bài viết này là:
JDK: BellSoft Liberica 17.0.7
Maven: 3.9.2
Spring Boot: 3.3.4
Thymeleaf: 3.1.2.RELEASE
Sau đây, chúng ta sẽ phân tích cấu trúc dự án và mã nguồn quan trọng của ứng dụng này.
1 Cấu trúc dự án
Dự án Web này được quản lý bằng Maven, với cấu trúc như sau:
spring-boot-thymeleaf-demo
├─ src/main
│ ├─ java
│ │ └─ com.example.demo
│ │ ├─ controller
│ │ │ ├─ BlogController.java
│ │ │ └─ HomeController.java
│ │ ├─ service
│ │ │ └─ BlogService.java
│ │ │ └─ impl
│ │ │ └─ BlogServiceImpl.java
│ │ ├─ model
│ │ │ └─ Blog.java
│ │ ├─ util
│ │ │ ├─ DateUtil.java
│ │ │ └─ IdGenerator.java
│ │ └─ DemoApplication.java
│ └─ resources
│ ├─ static
│ │ └─ css
│ │ └─ styles.css
│ └─ templates
│ ├─ blogs
│ │ ├─ add.html
│ │ ├─ blog.html
│ │ └─ blogs.html
│ ├─ error
│ │ └─ 404.html
│ ├─ home
│ │ └─ index.html
│ └─ layout.html
└─ pom.xml
Như bạn có thể thấy, tệp pom.xml
nằm trong thư mục gốc là tệp mô tả dự án Maven; mã nguồn Java nằm trong thư mục src/main/java
dưới gói com.example.demo
; thư mục con templates
trong src/main/resources
chứa các tệp mẫu Thymeleaf, còn thư mục con static
chứa tài nguyên tĩnh (ở đây chỉ có một tệp CSS).
Chỉ những phụ thuộc liên quan trong tệp pom.xml
được liệt kê và giải thích dưới đây. Các đoạn mã mẫu quan trọng khác về template và Java sẽ được thảo luận ở phần tiếp theo.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
</dependencies>
Có thể nhận thấy rằng ngoài phụ thuộc spring-boot-starter-web
cần thiết cho bất kỳ ứng dụng Web Spring Boot nào, để tích hợp Thymeleaf, chúng ta cần thêm phụ thuộc spring-boot-starter-thymeleaf
. Phụ thuộc thymeleaf-layout-dialect
giúp quản lý bố cục chung giữa các trang, giảm thiểu sự lặp lại mã nguồn. Cuối cùng, phụ thuộc lombok
giúp loại bỏ việc viết thủ công các phương thức Setters
và Getters
trong các lớp Model.
2 Phân tích mã nguồn quan trọng
2.1 Quản lý bố cục template
Do tất cả các trang đều có phần đầu và chân trang giống nhau, nếu viết riêng lẻ cho mỗi trang sẽ gây ra nhiều dư thừa mã nguồn. Vì vậy, chúng ta sử dụng thymeleaf-layout-dialect
để tạo một bố cục thống nhất, sau đó áp dụng cho các trang khác.
Mã nguồn của file bố cục chung src/main/resources/templates/layout.html
như sau:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<title>Tổng hợp blog</title>
<link rel="stylesheet" th:href="@{/css/styles.css}">
</head>
<body>
<header layout:fragment="header">
<nav>
Trang chủ
Nộp blog
Danh sách blog
</nav>
</header>
<main class="container" layout:fragment="content"></main>
<footer layout:fragment="footer">
<p th:text="@{'© {year} Tổng hợp blog'(year=${T(com.example.demo.util.DateUtil).getCurrentYear()})}"></p>
</footer>
</body>
</html>
Bạn có thể thấy rằng template bố cục này đã trỏ đến file CSS chung trong thẻ <head>
; đồng thời định nghĩa phần <header>
và <footer>
chung cho toàn bộ trang, trong khi mỗi trang chỉ cần thay đổi nội dung phần <main>
. Lưu ý rằng đường dẫn tới file CSS được thực hiện thông qua cú pháp th:href="@{/css/styles.css}"
của Thymeleaf. Ngoài ra, trong phần <footer>
, chúng ta gọi phương thức tĩnh getCurrentYear()
từ lớp DateUtil
thông qua biểu thức ${T(com.example.demo.util.DateUtil).getCurrentYear()}
.
2.2 Hiển thị nội dung động và gửi form
Sau khi giới thiệu về template bố cục chung, bây giờ chúng ta hãy tìm hiểu về trang danh sách blog và trang nộp blog, nhằm tập trung vào cách hiển thị nội dung động và gửi form.
Mã nguồn của trang danh sách blog src/main/resources/templates/blogs/blogs.html
như sau:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout::layout}">
<head>
<title>Danh sách blog</title>
</head>
<body>
<main layout:fragment="content">
<div class="blog-list">
<ul>
<li th:each="blog : ${blogs}" th:if="${!blog.deleted}">
<a th:href="@{/blogs/{id}(id=${blog.id})}" th:text="${blog.name}"></a>
</li>
</ul>
</div>
</main>
</body>
</html>
Trang danh sách blog sử dụng biểu thức layout:decorate="~{layout::layout}"
để kế thừa bố cục chung. Sau đó, tiêu đề trang được thay đổi trong thẻ <head>
, và nội dung phần <main>
được thay thế bằng danh sách các blog. Biểu thức th:each="blog : ${blogs}"
giúp duyệt qua danh sách các blog, trong khi điều kiện th:if="${!blog.deleted}"
đảm bảo chỉ hiển thị các blog không bị đánh dấu xóa.
Phần xử lý Controller tương ứng với trang danh sách blog và chi tiết blog như sau:
package com.example.demo.controller;
@Controller
@RequestMapping("/blogs")
public class BlogController {
@Autowired
private BlogService blogService;
@GetMapping("")
public String listAllBlogs(Model model) {
List<Blog> allBlogs = blogService.listAllBlogs();
model.addAttribute("blogs", allBlogs);
return "blogs/blogs";
}
@GetMapping("/{id}")
public String getBlogById(@PathVariable("id") Integer id, Model model) {
Optional<Blog> optional = blogService.getBlogById(id);
if (optional.isEmpty()) {
return "error/404";
}
model.addAttribute("blog", optional.get());
return "blogs/blog";
}
}
Để truyền dữ liệu từ Controller sang template, chúng ta cần thêm một đối số Model model
vào phương thức và đặt các giá trị tương ứng vào các thuộc tính của nó (model.addAttribute("blogs", allBlogs)
), từ đó có thể truy cập chúng trong template bằng tên thuộc tính (${blogs}
). Sau khi thiết lập xong, chỉ cần trả về tên của template tương ứng trong thư mục templates
.
Thymeleaf không chỉ hỗ trợ hiển thị trang web thông thường mà còn hỗ trợ tạo form, kiểm tra và gửi dữ liệu.
Mã nguồn của trang nộp blog src/main/resources/templates/blogs/add.html
như sau:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout::layout}">
<head>
<title>Nộp blog</title>
</head>
<body>
<main layout:fragment="content">
<div class="form">
<form th:action="@{/blogs}" th:object="${blog}" method="post">
<div>
<label>Tên blog:</label>
<span class="error" th:if="${#fields.hasErrors('name')}" th:errors="*{name}" />
</div>
<div>
<input name="name" th:field="*{name}"/>
</div>
<div>
<label>Mô tả blog:</label>
<span class="error" th:if="${#fields.hasErrors('description')}" th:errors="*{description}" />
</div>
<div>
<textarea name="description" th:field="*{description}"/>
</div>
<div>
<label>Blog kỹ thuật:</label>
</div>
<div>
<select id="options" name="technical">
<option value="false">Không</option>
<option value="true">Có</option>
</select>
</div>
<div>
<button>Gửi</button>
</div>
</form>
</div>
</main>
</body>
</html>
Trang này chứa một form với các trường nhập liệu như <input>
, <textarea>
và <select>
, cùng nút gửi <button>
. Form này sẽ gắn dữ liệu vào đối tượng blog
(th:object="${blog}"
) và gửi dữ liệu tới đường dẫn /blogs
bằng phương thức POST
.
Mã nguồn Controller tương ứng với trang này như sau:
package com.example.demo.controller;
@Controller
@RequestMapping("/blogs")
public class BlogController {
@Autowired
private BlogService blogService;
@GetMapping("/add-form")
public String addBlogForm(Blog blog) {
return "blogs/add";
}
@PostMapping("")
public String addBlog(Blog blog, Errors errors) {
// Kiểm tra lỗi đầu vào
if (StringUtils.isBlank(blog.getName())) {
errors.rejectValue("name", "fields.invalid", "Tên không được để trống");
return "blogs/add";
}
if (StringUtils.isBlank(blog.getDescription())) {
errors.rejectValue("description", "fields.invalid", "Mô tả không được để trống");
return "blogs/add";
}
// Lưu blog mới
blogService.addBlog(blog);
return "redirect:/blogs";
}
}
Phương thức addBlogForm()
hiển thị trang form, trong khi addBlog()
xử lý dữ liệu gửi từ form. Nếu có lỗi, thông tin lỗi sẽ được lưu vào đối tượng Errors
và trả về form để hiển thị.
Kết luận
Qua bài viết này, chúng ta đã học cách tích hợp Thymeleaf vào Spring Boot để xây dựng một ứng dụng Web thu thập blog. Nhìn chung, việc sử dụng Thymeleaf trong Spring Boot để phát triển ứng dụng Web rất tiện lợi và đáng cân nhắc.
Toàn bộ mã nguồn ví dụ đã được đưa lên GitHub, mời bạn đọc quan tâm hoặc Fork.
[1] Hướng dẫn: Sử dụng Thymeleaf -
#Java nhà cái 888b #Spring