第5章数据验证 视频讲解 数据验证可分为以下两种方式。 (1) 客户端验证(也称为前端验证): 在客户的浏览器端执行JavaScript等脚本代码,对用户输入的表单数据等进行数据验证。 (2) 服务器端验证(也称为后端验证): 在服务器端执行程序代码,对客户提供的请求数据或者其他业务数据进行验证。 客户端验证的优点是迅速、便捷,由于直接在浏览器端执行,因此具有更快的响应速度,并且能减轻服务器端的工作负荷; 缺点是数据验证的能力有限,而且不能确保通过验证的数据会完整地传送到服务器端。 服务器端验证适用于以下三种场合。 (1) 对某些数据的验证涉及访问服务器端的各种资源(如访问数据库),在浏览器端无法完成验证。 (2) 客户端进行验证通过的数据在经过网络传输时,有可能被非法篡改,为了安全,需要在服务器端再次对数据进行验证。 (3) 一些非法用户没有通过常规的浏览器程序访问服务器端,而是直接编写黑客程序,向服务器端发送非法的请求数据。 本书介绍的数据验证指的是服务器端验证。第2章的helloapp应用范例已经介绍了数据验证的基本执行流程。本章将进一步介绍Spring MVC框架所支持的数据验证方式,主要包括以下两种。 (1) 按照JSR303规范进行数据验证。 (2) 使用Spring框架自身提供的数据验证机制。 本章范例也位于helloapp应用中,主要包括以下4个组件。 (1) 视图组件: hello.jsp。它负责生成HTML表单,并且会显示数据验证产生的错误消息。 (2) 控制器组件: PersonController类。它负责对表单进行数据验证。 (3) 模型组件: Person类。它利用数据验证注解声明对特定的属性进行验证。 (4) 数据验证组件: Minimal类,它是自定义的数据验证注解类型,MinimalValidator类是实现@Minimal注解的验证功能的数据验证类; PersonValidator类,它是实现了Spring的Validator接口的数据验证类。 5.1按照JSR303规范进行数据验证 JSR303(Java Specification Request 303,Java 规范提案303)是Java领域的标准数据验证规范。JSR303 API位于Java EE类库的javax.validation包以及子包中。JSR303 API主要定义了一系列用于数据验证的注解,但是它并没有真正实现数据验证功能。 Hibernate Validator验证器是JSR303 API的具体实现,并且扩展了数据验证功能,提供了如@Email等实用的数据验证注解。 在Web应用中,联合使用JSR303 API和Hibernate Validator验证器,就能进行数据验证。 5.1.1数据验证注解 所有的数据验证注解都有一个message属性,用来指定验证失败的错误消息。message属性有以下两种赋值方式。 (1) 把错误消息的编号赋值给message属性。 (2) 直接把错误消息文本赋值给message属性。 以下代码通过这两种方式为message属性赋值。 //第一种方式:指定错误消息的编号 @NotBlank(message ="{person.no.username.error}") private String userName; //第二种方式:指定错误消息文本 @NotBlank(message = "UserName can’t be empty.") private String userName; 表51列出了JSR303提供的数据验证注解,它们可以对字符串、布尔、集合、数字、日期时间等类型的数据进行验证。表51注解的括号内列出的是注解的主要属性,例如@Min(value)注解的value指的是@Min注解的value属性。 表51JSR303提供的数据验证注解 数据验证注解描述 @Null 待验证数据必须为null @NotNull 待验证数据必须不为null @AssertTrue 待验证数据为布尔类型,并且必须为true @AssertFalse 待验证数据为布尔类型,并且必须为false @Min(value) 待验证数据必须大于或等于value 续表 数据验证注解描述 @Max(value) 待验证数据必须小于或等于value @DecimalMin(value) 待验证数据必须大于或等于value @DecimalMax(value) 待验证数据必须小于或等于value @Size(min,max) 待验证数据如果是String类型,那么其长度必须大于或等于min,并且小于或等于max; 待验证数据如果是集合、Map或数组类型,那么其包含的元素数目必须大于或等于min,并且小于或等于max @Digits(integer,fraction) 待验证数据必须是数字,integer指定数字的整数部分的最大位数,fraction指定数字的小数部分的最大位数。例如@Digits(integer=6,fraction=2)表示数字的整数部分最多6位,小数部分最多2位 @Past 待验证数据为日期时间类型,并且必须小于当前日期时间 @Future 待验证数据为日期时间类型,并且必须大于当前日期时间 @Pattern(regex) 待验证数据必须符合特定的正则表达式,regex指定正则表达式 @Valid 对待验证数据以及所关联的数据进行递归验证,参见5.1.4节 @Min(value)、@Max(value)、@DecimalMin(value)以及@DecimalMax(value)注解都能判断待验证数据是否小于或等于、大于或等于value。它们的区别如下。 (1) @Min(value)和@Max(value)的value属性为long类型,例如@Min(value=6)表示待验证数据必须大于或等于6。 (2) @DecimalMin(value)和@DecimalMax(value)的value属性为String类型,例如@ DecimalMin(value="6.0")表示待验证数据必须大于或等于6.0。 如果要了解JSR303提供的数据验证注解的更详细用法,可以参考Oracle官网提供的JavaDoc文档,参见图51。 图51JSR303提供的数据验证注解的JavaDoc文档 Hibernate Validator验证器也提供了一些实用的数据验证注解,参见表52。 表52Hibernate Validator提供的数据验证注解 数据验证注解描述 @NotBlank 待验证数据为String类型,去除两端空格后必须不为空。假定变量data为待验证数据,判断条件为(data !=null) && (data.trim().length()>0) @NotEmpty 待验证数据如果是String类型,必须不为空。假定变量data为待验证数据,判断条件为(data !=null) && (data.length()>0); 待验证数据如果是集合、Map或数组类型,那么其包含的元素的数目必须大于0 @Length(min,max) 待验证数据为String类型,其长度必须大于或等于min,小于或等于max @Range(min,max) 待验证数据为数字类型或可以转换为数字的String类型,其数值必须大于或等于min,小于或等于max @URL 待验证数据必须是有效的URL @Email 待验证数据必须是有效的Email @CreditCardNumber 待验证数据必须是有效的信用卡卡号 如果要了解Hibernate Validator验证器提供的数据验证注解的更详细用法,可以参考Hibernate提供的官方JavaDoc文档,网址为https://docs.jboss.org/hibernate/validator/7.0/api/。 在本章范例的Person类中,使用了来自JSR303和Hibernate Validator验证器的各种数据验证注解。例程51是Person类的部分代码。 例程51Person类的部分代码 public class Person{ @NotBlank(message = "{person.no.username.error}") private String userName; @Size(min=6,max=6,message="{person.tooshort.password.error}") private String password; @Email(message="{person.invalid.email.error}") private String email; @DecimalMin(value="0.0",message="{person.invalid.salary.error}") private double salary; @Minimal(value=1,message="{person.invalid.age.error}") private int age; … } Person类除了使用@Size和@Email等来自JSR303和Hibernate Validator验证器的注解,还使用了一个自定义的数据验证注解@Minimal,5.1.2节将介绍如何创建自定义的数据验证注解。 5.1.2自定义数据验证注解 JSR303和Hibernate Validator提供的数据验证注解是有限的。为了能实现特定的数据验证功能,JSR303还支持开发人员创建自定义的数据验证注解,包括以下两个步骤。 (1) 创建自定义注解类型,在本范例中为Minimal类。 (2) 创建数据验证实现类,在本范例中为MinimalValidator类。 1. 创建Minimal自定义注解类型 例程52是自定义的注解类型,它用@Constraint注解标识。来自javax.validation包的@Constraint注解表明Minimal类是用于数据验证的注解类型。@Constraint注解的validatedBy属性指定实现数据验证功能的数据验证类。 例程52Minimal.java @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy=MinimalValidator.class) public @interface Minimal { int value() default 0; String message(); Class[] groups() default {}; Class[] payload() default {}; } 2. 创建MinimalValidator数据验证实现类 例程53实现了javax.validation.ConstraintValidator接口,为@Minimal注解实现具体的数据验证功能。 例程53MinimalValidator.java package mypack; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class MinimalValidator implements ConstraintValidator { private int minValue; public void initialize(Minimal min) { //把Minimal注解的value属性赋值给成员变量minValue minValue = min.value(); } public boolean isValid(Integer value, ConstraintValidatorContext context) { //value参数表示被检验的数据 return value >= minValue; } } @Minimal注解的数据验证逻辑和JSR303的@Min注解相同,因此,在Person类中可以用@Minimal注解或者@Min注解来标识age属性,例如: @Minimal(value=1,message="{person.invalid.age.error}") private int age; 或者: @Min(value=1,message="{person.invalid.age.error}") private int age; 5.1.3在Spring MVC的配置文件中配置Hibernate Validator验证器 为了把Hibernate Validator整合到Spring MVC框架中,需要在Spring MVC的配置文件中进行如下配置: 元素向Spring MVC框架注册了Hibernate Validator验证器。的validator属性指向这个数据验证器。这样,当程序需要进行数据验证时,Spring MVC框架就会利用Hibernate Validator验证器完成实际的数据验证功能。 提示: 无论是使用了来自Hibernate Validator的数据验证注解,还是使用了来自JSR303或者自定义的数据验证注解,都必须在Spring MVC的配置文件中配置Hibernate Validator验证器,这样才能保证这些注解的正常工作。 5.1.4在控制器类中进行数据验证 在PersonController类中,用@Valid注解来标识greet()方法的person参数,例如: public String greet( @Valid @ModelAttribute("personbean")Person person, BindingResult bindingResult,Model model){…} @Valid注解来自JSR303 API的javax.validation包。@Valid注解的作用是对当前数据验证时,递归验证与当前数据关联的数据。例如在对greet()方法的person参数进行数据验证时,会递归验证它所引用的Person对象的所有属性,假如person参数引用的Person对象包含集合属性,那么还会对集合中的元素进行递归验证。 此外,在控制器类的方法中,还可以直接编写数据验证代码。例如,在以下greet()方法中,还会对Person对象的userName属性做进一步的数据验证,要求userName属性中不能包含Monster字符串。 @RequestMapping(value = "/sayHello", method = RequestMethod.POST) public String greet( @Valid @ModelAttribute("personbean")Person person, BindingResult bindingResult,Model model) { //直接在控制器类中提供的数据验证 if (person.getUserName()!=null && person.getUserName().indexOf("Monster")!=-1) { bindingResult.rejectValue("userName", "person.forbidden.username.error"); } if(bindingResult.hasErrors()){ return "hello"; } //调用Person对象的save()方法把Person对象保存到数据库中 person.save(); return "hello"; } org.springframework.validation.BindingResult接口的父接口是org.springframework.validation.Errors。Errors接口的rejectValue()方法生成错误消息,它有以下两种重载方法。 (1) rejectValue(String field, String errorCode): 参数errorCode指定错误消息编号。 (2) rejectValue(String field, String errorCode, String defaultMessage): 参数defaultMessage指定默认的错误消息文本。 5.1.5在JSP文件中指定显示错误消息的CSS样式 在JSP文件中通过标签输出错误消息,它的cssStyle属性和cssClass属性指定显示错误消息的CSS样式。 1. 用cssStyle属性指定显示错误消息的CSS样式 使用cssStyle属性设定CSS样式比较简单,例如以下代码指定用红色字体显示错误消息。 2. 用cssClass属性指定显示错误消息的CSS样式 以下JSP代码指定用error_class样式显示错误消息。 在error.css文件中定义了error_class样式。error.css文件的内容如下: .error_class { FONT-SIZE: 11px; COLOR: #FF0000; } error.css是静态资源文件,error.css的真实文件路径为helloapp/css/error.css。在Spring MVC的配置文件中进行了如下的相应配置: 对于本范例的hello.jsp,当用户在表单中输入了不合法的数据,hello.jsp会在网页上显示如图52所示的数据验证错误消息。 图52hello.jsp显示的数据验证错误消息 5.2Spring框架的数据验证机制 Spring框架本身也提供了一套数据验证机制。运用这套验证机制需要以下两个步骤。 (1) 创建实现org.springframework.validation.Validator接口的数据验证类。在本范例中为PersonValidator类。 (2) 在Spring MVC框架中创建数据验证类的对象并通过它进行数据验证。 5.2.1实现Spring的Validator接口 Spring API提供了一个数据验证接口: org.springframework.validation.Validator接口。例程54实现了Validator接口。PersonValidator类会验证Person类的userName属性和password属性。 例程54PersonValidator.java package mypack; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; public class PersonValidator implements Validator { public boolean supports(Class clazz) { return Person.class.equals(clazz); } public void validate(Object obj, Errors errors) { ValidationUtils.rejectIfEmpty(errors, "userName", "person.no.username.error"); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "person.no.password.error"); Person person=(Person) obj; if(person.getPassword() != null && person.getPassword()!="" && person.getPassword().length()!=6){ errors.rejectValue("password", "person.tooshort.password.error"); } } } PersonValidator类实现了Validator接口的以下两种方法。 (1) supports()方法: 指定PersonValidator类所验证的数据类型。 (2) validate()方法: 进行数据验证。 validate()方法首先利用ValidationUtils类来验证Person对象的userName属性和password属性。ValidationUtils类是Spring API提供的用于数据验证的实用类,它有以下两种常用的静态方法。 (1) rejectIfEmpty(): 如果待验证数据为空,就生成错误消息。验证逻辑等同于Hibernate Validator验证器的@NotEmpty注解。 (2) rejectIfEmptyOrWhitespace(): 如果待验证数据去除两端空格后为空,就生成错误消息。验证逻辑等同于Hibernate Validator验证器的@NotBlank注解。 ValidationUtils类的rejectIfEmpty()和rejectIfEmptyOrWhitespace()都有一些重载方法。以下是rejectIfEmpty()的两种常用的重载方法。 (1) rejectIfEmpty(Errors errors, String field, String errorCode): 参数errorCode指定错误消息编号。 (2) rejectIfEmpty(Errors errors, String field, String errorCode, String defaultMessage): 参数defaultMessage): 参数defaultMessage指定默认的错误消息文本。 5.2.2用数据验证类进行数据验证 创建以及使用PersonValidator对象有以下三种方式。 1. 把PersonValidator对象和PersonController的当前DataBinder对象绑定 在PersonController类中,以下initBinder()方法会把PersonBinder对象加入到当前的DataBinder对象中。 @InitBinder public void initBinder(DataBinder binder) { binder.setValidator(new PersonValidator()); } org.springframework.validation.DataBinder类是Spring框架提供的用来存放数据验证对象的容器类。initBinder()方法用@InitBinder注解标识,Spring MVC框架在每次调用PersonController的请求处理方法之前,先调用initBinder()方法创建PersonValidator对象,并把它与当前的DataBinder对象绑定。这个PersonValidator对象专门为PersonController的请求处理方法进行数据验证。 PersonController类的请求处理方法可以通过JSR303的@Valid注解来声明对person参数进行数据验证,例如: public String greet( @Valid @ModelAttribute("personbean")Person person, BindingResult bindingResult,Model model){…} @Valid注解声明要对person参数进行验证,Spring MVC框架就会利用与当前DataBinder对象绑定的PersonValidator进行相应的数据验证。 2. 注册PersonValidator Bean组件,并把它设为全局的数据验证类 在Spring MVC的配置文件中配置PersonValidator类的代码如下: 元素向Spring MVC框架注册了PersonValidator Bean组件,的validator属性指向这个PersonValidator Bean组件。这样,当控制器类需要对Person类的数据进行验证时,Spring MVC框架就会利用PersonValidator对象来完成实际的数据验证功能。按照这种方式配置的PersonValidator对象的数据验证范围是整个Web应用。 PersonController类的请求处理方法通过JSR303的@Valid注解来声明对person参数进行数据验证,例如: public String greet( @Valid @ModelAttribute("personbean")Person person, BindingResult bindingResult,Model model){…} @Valid注解声明要对person参数进行验证,Spring MVC框架就会利用全局范围内的PersonValidator类进行相应的数据验证。 值得注意的是,PersonValidator类并没有提供通用的数据验证功能,它只能对Person类的数据进行验证,实际上并不推荐把它作为全局的数据验证类。 3. 注册PersonValidator Bean组件,PersonController通过@Resource注解访问它 在Spring MVC的配置文件中配置PersonValidator类的代码如下: 元素向Spring MVC框架注册了PersonValidator Bean组件,Spring MVC框架会负责管理PersonValidator对象的生命周期。 在PersonController类中,通过来自javax.annotation包的@Resource注解访问由Spring MVC框架提供的PersonValidator对象,例如: @Controller public class PersonController { @Resource //由Spring MVC框架提供PersonValidator对象 private PersonValidator personValidator; @RequestMapping(value = {"/input","/"},method = RequestMethod.GET) public String init(Model model) { model.addAttribute("personbean",new Person()); return "hello"; } @RequestMapping(value = "/sayHello", method = RequestMethod.POST) public String greet( @ModelAttribute("personbean")Person person, BindingResult bindingResult,Model model) { personValidator.validate(person,bindingResult); if(bindingResult.hasErrors()){ return "hello"; } … } } PersonController类的personValidator成员变量用@Resource注解标识,意味着PersonController类无须创建PersonValidator对象,Spring MVC框架会把PersonValidator Bean组件赋值给personValidator成员变量。 5.3小结 本章介绍了数据验证的各种方式。表53比较了各种验证方式的特点以及优缺点。 表53各种数据验证方式的特点以及优缺点 数据验证方式优缺点 直接在控制器类中编写数据验证代码,参见5.1.4节 比较灵活,可以在请求处理方法的任何地方进行数据验证; 数据验证代码分散在各处,代码的可重用性和可维护性差 按照JSR303规范进行数据验证 具有很好的通用性和可重用性; 数据验证注解主要用来对JavaBean类的属性进行验证,不能在程序的任何地方使用该数据验证功能 使用Spring框架的数据验证机制 可以通过编程的方式实现复杂的数据验证逻辑,在数据验证类中可以方便地访问Spring框架提供的各种资源; 必须亲自编写程序代码来实现各种数据验证逻辑 对于规模比较小的Web应用,直接在控制器类中编写数据验证代码更加灵活、方便,避免了配置数据验证类的麻烦。 对于大型Web应用,为了促进软件应用的模块化和层次化,可以联合使用JSR303数据验证和Spring的自带验证机制: (1) 用JSR303的数据验证注解对常见的数据类型进行通用的数据验证。 (2) 对于部分需要定制的数据验证逻辑,则通过实现Spring的Validator接口来完成。 把这两种验证方式结合,就能满足各种数据验证的需求。 5.4思考题 1. ()属于JSR303的数据验证注解。(多选) A. @NotNullB. @Email C. @Past D. @Size 2. ()用@NotEmpty注解进行数据验证会通过验证。(多选) A. ""B. "" C. null D. "hello" 3. 按照JSR303规范创建自定义的数据验证注解时,完成实际验证功能的类应该实现或者继承()。(单选) A. javax.validation.Valid B. javax.validation.ConstraintValidator C. org.springframework.validation.Validator D. org.springframework.validation.ValidationUtils 4. ()属于org.springframework.validation.Validator接口的方法。(多选) A. validate()B. supports() C. message()D. value() 5. 对于BindingResult类型的bindingResult变量,()合法调用了它的rejectValue()方法。(多选) A. bindingResult.rejectValue("age"); B. bindingResult.rejectValue("age", "age.error"); C. bindingResult.rejectValue("age", null,"Invalid age"); D. bindingResult.rejectValue("age", "age.error","Invalid age");