1. 概述

在本教程中,我们将了解 如何在Thymeleaf模板中显示源自基于 Spring 的后端应用程序的错误消息

出于演示目的,我们将创建一个简单的 Spring Boot 用户注册应用程序并验证各个输入字段。此外,我们将看到如何处理全局级错误的示例。

首先,我们将快速设置后端应用程序,然后进入 UI 部分。

2. Spring Boot 应用程序示例

要创建一个用于用户注册的简单 Spring Boot 应用程序, 我们需要一个控制器、一个存储库和一个实体

然而,在此之前,我们应该添加 Maven 依赖项。

2.1. Maven依赖

让我们添加我们需要的所有 Spring Boot 启动器 - 用于 MVC 位的Web 、用于 hibernate 实体验证的Validation 、用于 UI 的Thymeleaf和用于存储库的JPA 。此外,我们需要H2依赖项才能拥有内存数据库:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.4.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.4.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.4.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>2.4.3</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
    <version>1.4.200</version>
</dependency>

2.2.实体

这是我们的 用户 实体:

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotEmpty(message = "User's name cannot be empty.")
    @Size(min = 5, max = 250)
    private String fullName;

    @NotEmpty(message = "User's email cannot be empty.")
    private String email;

    @NotNull(message = "User's age cannot be null.")
    @Min(value = 18)
    private Integer age;

    private String country;

    private String phoneNumber;

    // getters and setters
}

正如我们所看到的,我们 为用户输入添加了许多验证约束 。例如,字段不应为 null 或为空,并且具有特定的大小或值。

值得注意的是,我们没有对 国家电话号码 字段添加任何限制。这是因为我们将使用它们作为生成全局错误或与特定字段无关的错误的示例。

2.3.知识库

我们将使用一个简单的JPA 存储库来实现我们的基本用例:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {}

2.4.控制器

最后,为了在后端将所有内容连接在一起,让我们组合一个 UserController

@Controller
public class UserController {

    @Autowired
    private UserRepository repository;
    @GetMapping("/add")
    public String showAddUserForm(User user) {
        return "errors/addUser";
    }

    @PostMapping("/add")
    public String addUser(@Valid User user, BindingResult result, Model model) {
        if (result.hasErrors()) {
            return "errors/addUser";
        }
        repository.save(user);
        model.addAttribute("users", repository.findAll());
        return "errors/home";
    }
}

这里我们在路径 /add 定义一个 GetMapping 来显示注册表单。我们在同一路径上的 PostMapping 在提交表单时处理验证,如果一切顺利,随后将其保存到存储库。

3.带有错误消息的Thymeleaf模板

现在已经介绍了基础知识,我们来到了问题的关键,即创建 UI 模板并显示错误消息(如果有)。

让我们根据可以显示的错误类型逐步构建模板

3.1.显示字段错误

Thymeleaf 提供了一个内置的 field.hasErrors 方法,该方法根据给定字段是否存在错误返回一个布尔值。将其与 th:if 结合起来,我们可以选择显示错误(如果存在):

<p th:if="${#fields.hasErrors('age')}">Invalid Age</p>

接下来,如果我们 想添加任何样式,我们可以有条件地使用 th:class

<p  th:if="${#fields.hasErrors('age')}" th:class="${#fields.hasErrors('age')}? error">
  Invalid Age</p>

我们简单的嵌入 CSS 类 错误 将元素变成红色:

<style>
    .error {
        color: red;
    }
</style>

另一个 Thymeleaf 属性 th:errors 使我们能够显示指定选择器上的所有错误,例如 电子邮件:

<div>
    <label for="email">Email</label> <input type="text" th:field="*{email}" />
    <p th:if="${#fields.hasErrors('email')}" th:errorclass="error" th:errors="*{email}" />
</div>

在上面的代码片段中,我们还可以看到使用 CSS 样式的变化。这里 我们使用 th:errorclass ,它消除了我们使用任何条件属性来应用 CSS 的需要

或者,我们可以选择使用 th:each 迭代给定字段上的所有验证消息:

<div>
    <label for="fullName">Name</label> <input type="text" th:field="*{fullName}" 
      id="fullName" placeholder="Full Name">
    <ul>
        <li th:each="err : ${#fields.errors('fullName')}" th:text="${err}" class="error" />
    </ul>
</div>

值得注意的是,我们在这里使用了另一个 Thymeleaf 方法 fields.errors() 来收集后端应用程序为 fullName 字段返回的所有验证消息。

现在,为了测试这一点,让我们启动 Boot 应用程序并点击端点 http://localhost:8080/add

这是当我们根本不提供任何输入时页面的样子:

3.2.一次显示所有错误

接下来,让我们看看如何将所有错误消息显示在一个位置,而不是一一显示每条错误消息。

为此, 我们将使用 Thymeleaf 的 fields.hasAnyErrors() 方法

<div th:if="${#fields.hasAnyErrors()}">
    <ul>
        <li th:each="err : ${#fields.allErrors()}" th:text="${err}" />
    </ul>
</div>

正如我们所看到的,我们在这里使用了另一个变体 fields.allErrors() 来迭代 HTML 表单上所有字段的所有错误。

我们可以使用 #fields.hasErrors('*') 来代替 fields.hasAnyErrors() 。类似地, #fields.errors('*') 是上面使用的 #fields.allErrors() 的替代方案。

效果如下:

3.3.在表单之外显示错误

下一个。让我们考虑一个场景,其中我们想要在 HTML 表单之外显示验证消息。

在这种情况下, * 而不是使用选择或 (*{….}) ,我们只需使用 (${….}) * 格式的完全限定变量名称:

<h4>Errors on a single field:</h4>
<div th:if="${#fields.hasErrors('${user.email}')}"
 th:errors="*{user.email}"></div>
<ul>
    <li th:each="err : ${#fields.errors('user.*')}" th:text="${err}" />
</ul>

这将在 电子邮件 字段中显示所有错误消息。

现在, 让我们看看如何一次显示所有消息

<h4>All errors:</h4>
<ul>
<li th:each="err : ${#fields.errors('user.*')}" th:text="${err}" />
</ul>

这是我们在页面上看到的内容:

3.4.显示全局错误

在现实生活中,可能存在与特定字段没有特定关联的错误。我们可能有一个用例, 需要考虑多个输入才能验证业务条件 。这些称为全局错误。

让我们考虑一个简单的例子来证明这一点。对于我们的 国家 /地区和 电话号码 字段,我们可能会添加一项检查,以确保对于给定国家/地区,电话号码应以特定前缀开头。

我们需要在后端进行一些更改才能添加此验证。

首先,我们将添加一个 服务 来执行此验证:

@Service
public class UserValidationService {
    public String validateUser(User user) {
        String message = "";
        if (user.getCountry() != null && user.getPhoneNumber() != null) {
            if (user.getCountry().equalsIgnoreCase("India") 
              && !user.getPhoneNumber().startsWith("91")) {
                message = "Phone number is invalid for " + user.getCountry();
            }
        }
        return message;
    }
}

正如我们所看到的,我们添加了一个简单的案例。对于 印度 国家,电话号码应以前缀 91 开头。

其次,我们需要调整控制器的 PostMapping

@PostMapping("/add")
public String addUser(@Valid User user, BindingResult result, Model model) {
    String err = validationService.validateUser(user);
    if (!err.isEmpty()) {
        ObjectError error = new ObjectError("globalError", err);
        result.addError(error);
    }
    if (result.hasErrors()) {
        return "errors/addUser";
    }
    repository.save(user);
    model.addAttribute("users", repository.findAll());
    return "errors/home";
}

最后,在 Thymeleaf 模板中, 我们将添加全局 常量 来显示此类错误

<div th:if="${#fields.hasErrors('global')}">
    <h3>Global errors:</h3>
    <p th:each="err : ${#fields.errors('global')}" th:text="${err}" class="error" />
</div>

或者,我们可以使用方法 #fields.hasGlobalErrors()#fields.globalErrors() 来代替常量来实现相同的目的。

这是我们在输入无效输入时看到的结果:

4。结论

在本教程中,我们构建了一个 简单的 Spring Boot 应用程序来演示如何在 Thymeleaf 中显示各种类型的错误

我们研究了一项一项地显示字段错误,然后一次全部显示、HTML 表单之外的错误以及全局错误。

与往常一样,源代码可在 GitHub 上获取。