本系列文章主要索引详情 点击查看
通常情况下,我们都不希望用户输入非法的信息,这样的话,我们就需要对表单添加一些校验逻辑。
工具
IntelliJ IDEA 16
JDK 1.8
Maven 3.5
Tomcat 1.8
表单校验
我们可以通过添加一些注解,来辅助完成校验限制。
1、首先我们需要在DTO的ProfileForm类的属性字段上添加限制注解:
package com.example.dto;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.time.LocalDate;
public class ProfileForm {
@Size(min = 2)
private String twitterHandle;
@Email
@NotNull
private String email;
@NotNull
private LocalDate birthDate;
public String getTwitterHandle() {
return twitterHandle;
}
public void setTwitterHandle(String twitterHandle) {
this.twitterHandle = twitterHandle;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
@Override
public String toString() {
return "ProfileForm{" +
"twitterHandle='" + twitterHandle + '\'' +
", email='" + email + '\'' +
", birthDate='" + birthDate + '\'' +
'}';
}
}
这些注解来源于JSR-303规范,它详细规定了bean的校验功能,这个规范最流行的实现是hibernate-validator,它已经包含在了Spring Boot之中。
我们使用了来自javax.validation.constraints包中的注解(在API中定义的)和org.hibernate.validator.constraints包的注解(额外限制)。我们可通过查阅validation-api和hibernate-validator的jar包文件来查看都有哪些可用注解。我们还可以查阅hibernate-validator的文档来了解它提供的可用限制。
2、完成了对DTO属性的限制,我们还学要在控制器中还需要声明在表单提交时,希望得到一个合法的模型。在提交方法的表单的参数上添加@Valid注解:
package com.example.controller;
import com.example.date.LocalDateFormatter;
import com.example.dto.ProfileForm;
import java.util.Locale;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.validation.Valid;
@Controller
public class ProfileController {
@RequestMapping("/profile")
public String displayProfile(ProfileForm profileForm){
return "profile/profilePage";
}
@RequestMapping(value = "/profile" ,method = RequestMethod.POST)
public String saveProfile(@Valid ProfileForm profileForm, BindingResult bindingResult){
if(bindingResult.hasErrors()){
return "profile/profilePage";
}
System.out.println("Save Ok"+profileForm);
return "redirect:/profile";
}
@ModelAttribute("dataFormat")
public String localeFormat(Locale locale){
return LocalDateFormatter.getPattern(locale);
}
}
@Valid注解 : javax.validation.Valid , 声明表单提交时,进行校验,从而得到是否是一个合法的模型。
BindingResult : Spring验证的错误返回,来将验证错误的信息返回到页面。
注意:如果表单中包含错误信息,我们不进行重定向,而是直接返回到同一个web页面中,并在页面中显示错误。
3、最后我们需要在Web页面中添加一个位置来展现这些错误,profilePage.html文件中,表单标签开始的地方添加如下代码:
<ul th:if="${#fields.hasErrors('*')}" class="errorlist">
<li th:each="err:${#fields.errors('*')}" th:text="${err}">input is incorrect</li>
</ul>
注意:这段代码必须添加在<form>...</form>标签中,如果添加在<form>...</form>之外,将会出现如下异常:
Could not bind form errors using expression "*". Please check this expression is being executed inside the adequate context (e.g. a <form> with a th:object attribute)
4、如果我们不输入任何数据,直接点提交,则会显示如下错误提示:
5、如果我们输入的信息不合法,则会显示如下:
自定义校验信息
从上面我们可以看出,这些显示的错误信息对我们并没有什么用处,我们并不能完全区分出这些错误信息是针对哪个输入域,所以,接下来我们需要完成的一项工作就是将错误提示信息和对应的输入域关联起来,帮助我们更清晰的了解我们的问题出在哪里。
按照如下方式修改profilePage.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http:www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="layout/default">
<head>
<meta charset="UTF-8"/>
<title>Your profile</title>
</head>
<body>
<div class="row" layout:fragment="content">
<h2 class="indigo-text center">Prosonal info</h2>
<!--/*@thymesVar id="profileForm" type="com.example.dto.ProfileForm"*/-->
<form th:action="@{/profile}" th:object="${profileForm}" method="post" class="col m8 s11 offset-m2" >
<div class="row">
<div class="input-field col s6">
<input th:field="${profileForm.twitterHandle}" id="twitterHandle" type="text" th:errorclass="invalid"/>
<label for="twitterHandle">Last name</label>
<div th:errors="*{twitterHandle}" class="red-text">Error</div>
</div>
<div class="input-field col s6">
<input th:field="${profileForm.email}" id="email" type="text" th:errorclass="invalid"/>
<label for="email">Email</label>
<div th:errors="*{email}" class="red-text">Error</div>
</div>
</div>
<div class="row">
<div class="input-field col s6">
<input id="birthDate" type="text" th:field="${profileForm.birthDate}" th:placeholder="${dataFormat}" th:errorclass="invalid"/>
<label for="birthDate">Birth Date</label>
<div th:errors="*{birthDate}" class="red-text">Error</div>
</div>
</div>
<div class="row s12">
<button class="btn waves-effect waves-light" type="submit" name="save">Submit<i class="mdi-content-snd right"></i> </button>
</div>
</form>
</div>
</body>
</html>
在表单的每个输入域中添加 th:errorclass属性,并在输入域的下面新增一个th:errors标签,如果输入域包含错误的话,将会有如下显示:
现在我们已经将错误提示信息和输入域进行了绑定,但是我们可以发现,有些提示信息并不适合展示给用户,所以我们需要对错误提示信息进行自定义。
方法一:
Spring Boot会负责为我们创建信息源bean,信息源的默认位置是 src/main/resource/messages.properties,如果我们没有看到这个文件,则创建messages.properties,并添加以下文本:
Size.profileForm.twitterHandle=Please type in your twitter user name
Email.profileForm.email=Please specify a valid email address
NotNull.profileForm.email=Please specify your email address
PastLocalDate.profileForm.birthDate=Please specify a real birth date
NotNull.profileForm.birthDate=Please specify your birth date
typeMismatch.birthDate=Invalid birth date format
以上文本定义了错误类型和对应的输入域,以及错误提示信息,并按照键值对的形式存在,解析错误时,根据错误类型,返回对应的错误提示信息到页面。
在Spring中负责解析错误信息的类是DefaultMessageCodesResolver。在进行输入域校验的时候,这个类将会按照如下顺序来尝试解析信息:
1、编码 + “.” + 对象 + “.” + 输入域 (例: Size.profileForm.twitterHandle)
2、编码 + “.” + 输入域
3、编码 + “.” + 输入域类型
4、编码
现在我们将会看到我们自定义的错误信息了:
在开发期,我们可以将信息源配置为每次都重新加载,我们只需要在application.properties文件中,添加
spring.messages.cache-seconds=0
其中 0 表示每次都重新加载,而 -1 则表示不进行重新加载。
方法二:
有了上面的信息,我们接下来可以让它更为具体,定义默认信息的最佳实践如下所示:
Size=the {0} field must be between {2} and {1} characters long
注意这里的占位符,每个校验错误都有与之相关联的一组参数,通过运行结果我们可以有清晰的了解:
方法三:
声明错误的最后一种方法是直接在检验注解中定义错误信息:
@Size(min = 2 ,message = "Please specify a valid twitter handle")
但是这种方式的缺点在于它无法与国际化功能兼容。
客户端校验
通过使用HTML5的表单校验规范,现在实现客户端校验已经非常容易了,如果浏览器是Internet Explorer 10 及以上的话,通过添加客户端校验只需要指定正确的输入域类型,不再讲type属性设置为text。
通过添加客户端校验,我们可以预先校验表单,避免已知的不正确的请求对服务器造成过大的负载。
我们可以修改输入域来启动简单的客户端校验。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http:www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="layout/default">
<head>
<meta charset="UTF-8"/>
<title>Your profile</title>
</head>
<body>
<div class="row" layout:fragment="content">
<h2 class="indigo-text center">Prosonal info</h2>
<!--/*@thymesVar id="profileForm" type="com.example.dto.ProfileForm"*/-->
<form th:action="@{/profile}" th:object="${profileForm}" method="post" class="col m8 s11 offset-m2" >
<div class="row">
<div class="input-field col s6">
<input th:field="${profileForm.twitterHandle}" id="twitterHandle" type="text" required="required" th:errorclass="invalid"/>
<label for="twitterHandle">Last name</label>
<div th:errors="*{twitterHandle}" class="red-text">Error</div>
</div>
<div class="input-field col s6">
<input th:field="${profileForm.email}" id="email" type="email" required="required" th:errorclass="invalid"/>
<label for="email">Email</label>
<div th:errors="*{email}" class="red-text">Error</div>
</div>
</div>
<div class="row">
<div class="input-field col s6">
<input id="birthDate" type="text" th:field="${profileForm.birthDate}" th:placeholder="${dataFormat}" required="required" th:errorclass="invalid"/>
<label for="birthDate">Birth Date</label>
<div th:errors="*{birthDate}" class="red-text">Error</div>
</div>
</div>
<div class="row s12">
<button class="btn waves-effect waves-light" type="submit" name="save">Submit<i class="mdi-content-snd right"></i> </button>
</div>
</form>
</div>
</body>
</html>
在每个表单输入域中添加属性 required="required",将强制用户输入非空的值,而修改type=“email”,会为对应的输入域进行基本的E-mail格式校验。