在 Spring 中使用 Thymeleaf 显示错误信息

1、概览

本文将带你了解如何在 Spring 应用中使用 Thymeleaf 模板来渲染错误信息。

我们会通过一个简单的 Spring Boot 项目来进行演示,该项目是一个 “用户注册” 应用,需要验证客户端传递的各个字段,还要处理全局错误。

2、Spring Boot 应用示例

创建一个简单的 Spring Boot 用户注册应用,需要一个 Controller、一个 Repository 和一个 Entity。

2.1、Maven 依赖

添加所有需要的 Spring Boot Starter:Web MvcHibernate ValidationThymeleafJPA

此外,还需要一个 H2 内存数据库依赖:

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

2.2、实体

User 实体如下:

@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;

    // get、set 方法省略
}

如你所见,为用户输入添加了许多验证约束。例如,字段不得为 null 或空,并限定了字符串的长短。

注意,没有对 countryphoneNumber 字段添加任何约束。这是因为我们将以它们作为示例来生成全局错误,或者与特定字段无关的错误。

2.3、Repository

本例中,使用一个简单的 JPA Repository:

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

2.4、Controller

创建一个 UserController,注入 UserRepository

@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 用于在提交表单时进行验证,如果一切顺利,则随后保存到 Repository。

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 class error 会将元素变为红色:

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

Thymeleaf 的另一个属性 th:errors 能够显示指定 selector(例如 email)上的所有错误:

<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 遍历给定字段上的所有验证信息(validation message):

<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 端点。

如下就是未提供任何输入时的 UI 界面。

Thymeleaf fieldErrs

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()

效果如下:

Thymeleaf allErrors

3.3、在表单外显示错误

考虑一下在 HTML 表单外显示验证信息的情况。

在这种情况下,不要使用 selection 或 (*{...}),只需使用格式为 (${...}) 的全限定变量名即可:

<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>

这会在 email 字段中显示所有错误信息。

现在,来看看如何一次性显示所有信息:

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

页面上看到的内容如下:

Thymeleaf outsideForm

3.4、显示全局错误

在实际情况中,可能会存在与特定字段无关的错误。我们可能会遇到一种情况,需要考虑多个输入以验证业务条件。这些错误被称为全局错误。

举一个简单的例子。对于 countryphoneNumber 字段,可以添加一个检查,即对于给定的国家,电话号码应以特定的前缀开头。

首先,添加一个 Service 来执行验证:

@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 开头。

然后,调整 Controller 的 @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 模板中,添加常量 global 来显示此类错误:

<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() 方法来代替常量,达到同样的目的。

在输入无效输入时看到的结果如下:

Thymeleaf global|390x375

4、总结

本文介绍了如何在 Spring Boot 中使用 Thymeleaf 显示错误信息,包括显示单个字段错误、一次性显示所有字段错误、在 HTML 表单外显示错误以及显示全局错误。


Ref:https://www.baeldung.com/spring-thymeleaf-error-messages