本文将带你了解如何在
Spring
应用中使用 Thymeleaf 模板来渲染错误信息。
我们会通过一个简单的 Spring Boot 项目来进行演示,该项目是一个 “用户注册” 应用,需要验证客户端传递的各个字段,还要处理全局错误。
2、Spring Boot 应用示例
创建一个简单的 Spring Boot 用户注册应用,需要一个 Controller、一个 Repository 和一个 Entity。
2.1、Maven 依赖
添加所有需要的 Spring Boot Starter:
Web Mvc
、
Hibernate Validation
、
Thymeleaf
和
JPA
。
此外,还需要一个
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 {
@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
或空,并限定了字符串的长短。
注意,没有对
country
或
phoneNumber
字段添加任何约束。这是因为我们将以它们作为示例来生成全局错误,或者与特定字段无关的错误。
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
)上的所有错误:
<
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):
<
label
for
=
"fullName"
>Name</
label
> <
input
type
=
"text"
th:field
=
"*{fullName}"
id
=
"fullName"
placeholder
=
"Full Name"
>
<
li
th:each
=
"err : ${#fields.errors('fullName')}"
th:text
=
"${err}"
class
=
"error"
/>
</
div
>
注意,在这里使用了另一个 Thymeleaf 方法
fields.errors()
,以收集后端应用针对
fullName
字段返回的所有验证信息。
现在,进行测试。启动 Boot 应用并访问
http://localhost:8080/add
端点。
如下就是未提供任何输入时的 UI 界面。
3.2、一次性显示所有错误
接下来,看看如何将所有错误信息集中显示,而不是逐一显示。
为此,可以使用 Thymeleaf 的
fields.hasAnyErrors()
方法:
<div th:if="${#fields.hasAnyErrors()}">
<li th:each="err : ${#fields.allErrors()}" th:text="${err}" />
</div>
如你所见,在这里使用了另一个变量
fields.allErrors()
,以遍历 HTML 表单上所有字段的所有错误。
可以使用
#fields.hasErrors('*')
代替
fields.hasAnyErrors()
。同样,
#fields.errors('*')
也可以替代上面使用的
#fields.allErrors()
。
效果如下:
3.3、在表单外显示错误
考虑一下在 HTML 表单外显示验证信息的情况。
在这种情况下,不要使用 selection 或
(*{...})
,只需使用格式为
(${...})
的全限定变量名即可:
<h4>Errors on a single field:</h4>
<div th:if="${#fields.hasErrors('${user.email}')}"
th:errors="*{user.email}"></div>
<li th:each="err : ${#fields.errors('user.*')}" th:text="${err}" />
这会在
email
字段中显示所有错误信息。
现在,来看看如何一次性显示所有信息:
<h4>All errors:</h4>
<li th:each="err : ${#fields.errors('user.*')}" th:text="${err}" />
页面上看到的内容如下:
3.4、显示全局错误
在实际情况中,可能会存在与特定字段无关的错误。我们可能会遇到一种情况,需要考虑多个输入以验证业务条件。这些错误被称为全局错误。
举一个简单的例子。对于
country
和
phoneNumber
字段,可以添加一个检查,即对于给定的国家,电话号码应以特定的前缀开头。
首先,添加一个 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()
方法来代替常量,达到同样的目的。
在输入无效输入时看到的结果如下:
本文介绍了如何在 Spring Boot 中使用 Thymeleaf 显示错误信息,包括显示单个字段错误、一次性显示所有字段错误、在 HTML 表单外显示错误以及显示全局错误。
Ref:
https://www.baeldung.com/spring-thymeleaf-error-messages