上一节中,我们已经从客户端提交了注册的表单到服务器,这样便建立了浏览器和服务器的双向通信,借此已经足以搭建一个Web应用了。从这一节开始,我们将针对Web业务中的基本问题,介绍相关的技术。
现在我们考虑,上一节的表单提交中,如果用户提交一个错误的表单情况会怎样?
为了解决这一问题,需要引入web应用中不可或缺的技术:表单验证。表单验证分为客户端验证和服务器端验证。
客户端验证即可解决上述第一个问题。当用户填写的表单存在明显错误时,浏览器端Javascript提示错误信息,并阻止表单提交。这样既避免了页面跳转更加用户友好,又减少了服务器的负载。
服务器端验证即可防止恶意的攻击。另外,有些验证过程客户端无法完成,比如用户名已存在、密码错误等。
提高
在成熟的项目中通常会引入第三方Javascript框架进行表单验证。例如各式各样的jQuery表单验证插件、还有各种前端框架提供的表单验证。HTML5的出现增强了HTML原生的表单验证,但因其使用方式较复杂、浏览器支持尚未完善、功能的欠缺等原因,尚未广泛使用。
在这一节课中我们重点来讲解如何在服务端利用Spring来完成数据的绑定与校验
处理POST请求与数据绑定中已经介绍过,Spring支持在@RequestMapping
注解的方法中使用对象来进行参数绑定:
Blog
对象中有两个字段title
和content
,Spring会将HTTP请求中的数据title
和content
自动注入(根据名字进行匹配)到Blog
对象中。
对于用户提交的数据,不能够直接将他们不加检验的存储起来。假设现在要求文章的标题长度必须在2-30个字符之间,文章内容不能为空。一种最直观的办法就是在Controller方法中进行判断:
@PostMapping(value = "/blogs")
public String create(Blog blog) {
if (validate(blog)) {
// normal handling
} else {
//error handling
}
// return
}
validate()
方法中可以进行各种检查,但是这种方法的问题在于——如果字段过多,那么需要编写很多if-else
语句;另一方面,这一类数据校验逻辑大多数是通用的,例如创建文章的表单数据和用户注册的表单数据基本都需要验证数据长度,在每一个方法中都编写一个验证逻辑显然是非常重复的。为了解决这个问题,可以使用Java Validation API
:
import javax.validation.constraints.Size;
public class Blog {
private Long id;
@Size(min=2, max=30)
private String title;
@Size(min=1)
private String content;
//其余方法、字段省略
}
@Size(min=2, max=30)
表示对应属性的字符串长度必须在2到30之间。
此外,可以通过message
属性来设置验证的信息,比如@Size(min=2, max=30, message="博客标题长度为2-30")
,那么验证失败时将可以返回message
信息。
当然,用于描述验证的规则的标注还有很多,大家可以在这里了解,今后的学习中我们还会介绍更多注解的应用场景。
但我们知道,注解仅仅是对类加上了一些元信息,如果不使用反射等API对其进行探测、处理,和不加注解没有任何区别。所以在Controller方法中需要告知Spring框架进行数据校验:
Spring一旦发现@Valid
注解,就会根据Blog
类中的注解规则进行数据校验,所以现在重新启动应用,再次提交表单,如果数据不符合校验规则(比如输入长度为1的博客标题),会出现如下错误提示:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Fri Aug 21 10:09:54 CST 2015
There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='post'. Error count: 2
有时表单绑定的对象可以专门创建一个类来表示,比如UserForm
用以表示Web请求传上来的用户信息,则相关的Java验证标注应用于UserForm
即可。
在复杂的应用中一般都会区分UserForm
和User
,因为两者往往不一致,比如用户注册是需要输入两次密码,则UserForm
类中可以添加一个repeatedPassword
属性,但是User
类中并不需要。甚至一个Model往往对应于多个Form,比如UserLoginForm
和UserRegisterForm
。
另外一种需要专门的Form对象的情况就是我们做练习的这种场景。因为Blog
在第三方的JAR中引入,我们并不能直接修改其代码。所以需要创建BlogCreateForm
来获取表单数据:
import javax.validation.constraints.Size;
public class BlogCreateForm {
@Size(min=2, max=30)
private String title;
@Size(min=1)
private String content;
//其余方法、字段省略
// 将BlogCreateForm转换为Blog的方法
Blog toBlog() {
Blog blog = new Blog();
blog.setTitle(this.title);
...
return blog;
}
}
相应地,就需要在BlogCreateForm
中提供一个将自身转换为Blog
的方法,而这个方法在Controller中调用。
很显然,Spring发现了用户提交的错误数据,但是返回这样的错误页面给用户,会让用户一头雾水不知所云。所以一旦发现错误以后,需要重新返回创建文章的页面并把错误提示给用户:
@PostMapping(value = "/")
public String create(@Valid Blog blog, BindingResult result) {
if (result.hasErrors()) {
return "create";
}
//save title and content to repository
return "redirect:/blogs/" + blog.getId();
}
在模板中可以这样获取这些错误信息,下面是简化的HTML示例:
<form th:action="@{/blogs}" th:object="${blog}" method='post'>
<input type="text" th:field="*{title}">
<p th:if="${#fields.hasErrors('title')}" th:errors="*{title}">标题长度必须在2-30之间</p>
<input type="text" th:field="*{content}">
<p th:if="${#fields.hasErrors('content')}" th:errors="*{content}">内容不可为空</p>
<input type="submit">
</form>
th:object="${blog}"
表示这是一个bean-backed的表单,在上一次练习中已经学习过,表示在每个表单域的后面,都跟随着一个<p>
元素来显示错误验证错误信息,比如<p th:if="${#fields.hasErrors('title')}" th:errors="*{title}">标题长度必须在2-30之间</p>
。
经过上述处理,再次提交表单就可以发现用户的错误数据能够很好地被处理了。
登录发表评论 登录 注册