Spring MVC+FastJson+hibernate-validator整合的完整实例教程
作者:vbirdbest
一:hibernate-validator 基础
1. 简介:
通过使用注解Annotations 给类或者类的属性加上约束(constraint),在运行期检查属性值的合法性.
2. 作用:
在API接口开发中参数校验是非常重要的事情,因为客户端很可能会少传参数,或者值不合法,甚至参数值是恶意的,所以对客户端传来的参数的合法性就必须要校验了,其中将参数值的校验规则通过注解的形式注解到属性上是一种比较优雅的方式。
3. 常用的约束注解
- @Null 被注释的元素必须为 null
- @NotNull 被注释的元素必须不为 null
- @AssertTrue 被注释的元素必须为 true
- @AssertFalse 被注释的元素必须为 false
- @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
- @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
- @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
- @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
- @Size(max=, min=) 被注释的元素的大小必须在指定的范围内
- @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
- @Past 被注释的元素必须是一个过去的日期
- @Future 被注释的元素必须是一个将来的日期
- @Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
- Hibernate Validator 附加的 constraint
- @NotBlank(message =) 验证字符串非null,且长度必须大于0
- @Email 被注释的元素必须是电子邮箱地址
- @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
- @NotEmpty 被注释的字符串的必须非空
- @Range(min=,max=,message=) 被注释的元素必须在合适的范围内
- @URL(protocol=,host=, port=, regexp=, flags=) 被注释的字符串必须是一个有效的url
- @CreditCardNumber 被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性
- @ScriptAssert(lang=, script=, alias=) 要有Java Scripting API 即JSR 223 (“Scripting for the JavaTM Platform”)的实现
- @SafeHtml(whitelistType=, additionalTags=) classpath中要有jsoup包
4. 初识hibernate-validator
public class Address { @NotNull private String line1; private String line2; private String zip; private String state; @Length(max = 20) @NotNull private String country; @Range(min = -2, max = 50, message = "Floor out of range") public int floor; // getter&&setter }
二:整合校验 hibernate-validator
该示例是在SpringMVC+FastJson整合(https://www.jb51.net/article/139094.htm)基础上进行集成的,可以先看一下这篇文章(有源码供下载),在该文章的基础上整合hibernate-validator
整合步骤:
1、在pom.xml中引入hibernate-validator依赖
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.4.1.Final</version> </dependency>
2、在[xxx]-servlet.xml中配置验证器:HibernateValidator
<!-- <mvc:annotation-driven> 增加验证器属性validator="validator" --> <mvc:annotation-driven validator="validator"> <mvc:message-converters register-defaults="true"> <!-- 配置Fastjson 替换原来的jackson支持 --> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> <value>application/json</value> </list> </property> <property name="features"> <list> <value>QuoteFieldNames</value> <value>WriteMapNullValue</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"> <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/> <property name="validationMessageSource" ref="messageSource"/> </bean> <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basenames"> <list> <value>classpath:conf/settings/validation</value> <value>classpath:org/hibernate/validator/ValidationMessages</value> </list> </property> <property name="useCodeAsDefaultMessage" value="false"/> <property name="defaultEncoding" value="UTF-8"/> <property name="cacheSeconds" value="60"/> </bean>
3、在src/main/resources/conf/settings位置定义验证消息文件描述 validation.properties
validation.common.not.null=该字段不能为空 validation.common.not.range=长度非法 validation.common.format.error=格式错误 validation.param.age=年龄未满18周岁 rep.error.unknown=未知错误
4、新建实体类以供测试
UserEntity
package com.mengdee.manage.validator; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Null; import javax.validation.constraints.Pattern; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotBlank; public class UserEntity { @Null(groups={GroupA.class}) @NotNull(groups={GroupB.class}) @Min(value = 1, message="id值必须大于0", groups={GroupB.class}) private Long id; @NotBlank(groups={GroupA.class, GroupB.class}) @Pattern(regexp="^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$", message="由6-21字母和数字组成,不能是纯数字或纯英文", groups={GroupA.class, GroupB.class}) private String password; @NotBlank(groups={GroupA.class, GroupB.class}) @Pattern(regexp="^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$", message="手机号格式不正确") private String phone; @NotBlank(groups={GroupB.class}) @Length(min=6, max=12, message="昵称长度为6到12位") private String nickname; @Min(value=18, message="{validation.param.age}") private int age; @NotBlank(groups={GroupA.class}) @Email(message="{validation.common.format.error}") private String email; @NotBlank @Length(min=3, max=10, message="{validation.common.not.range}") private String username; public UserEntity() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } }
UserModel
package com.mengdee.manage.validator; import javax.validation.constraints.Min; import javax.validation.constraints.Pattern; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotBlank; public class UserModel { @Min(value = 1, message="id值必须大于0") private long id; @NotBlank @Length(min=6, max=12, message="昵称长度为6到12位") private String nickname; @Min(value=18, message="{validation.param.age}") private int age; @NotBlank @Email(message="{validation.common.format.error}") private String email; @NotBlank @Pattern(regexp="^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$", message="由6-21字母和数字组成,不能是纯数字或纯英文") private String password; @NotBlank @Length(min=3, max=10, message="{validation.common.not.range}") private String username; @NotBlank @Pattern(regexp="^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$", message="手机号格式不正确") private String phone; public UserModel() { } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } }
UserDetail :
package com.mengdee.manage.validator; import org.hibernate.validator.constraints.NotBlank; public class UserDetail { private Long id; @NotBlank private String address; @NotBlank private String company; public UserDetail() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getCompany() { return company; } public void setCompany(String company) { this.company = company; } }
使用ValidController 进行测试
package com.mengdee.manage.controller; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import com.mengdee.manage.validator.UserModel; @Controller @RequestMapping("/Valid") public class ValidController extends BaseController { // http://localhost:8081/platform-springmvc-webapp/Valid/test?age=18&nickname=mengdee&id=1&email=123@qq.com&password=root123&username=123&phone=18321758957 @RequestMapping(value = "/test", method = RequestMethod.GET) public @ResponseBody Object validation(HttpServletRequest request, @Valid UserModel user, BindingResult bindingResult){ if (bindingResult.hasErrors()) { FieldError fieldError = bindingResult.getFieldError(); return super.ResponseJsonError(fieldError); } return "ok"; } // 一个方法同时校验多个对象需要绑定多个结果BindingResult,出现一个@Valid就对应后面声明的一个BindingResult @RequestMapping(value = "/test2", method = RequestMethod.GET) public @ResponseBody Object validationMore(HttpServletRequest request, @Valid UserModel user, BindingResult bindingResult, @Valid UserDetail userDetail, BindingResult bindingResult2){ if (bindingResult.hasErrors()) { FieldError fieldError = bindingResult.getFieldError(); return super.ResponseJsonError(fieldError); } if (bindingResult2.hasErrors()) { FieldError fieldError = bindingResult2.getFieldError(); return super.ResponseJsonError(fieldError); } return "ok"; } }
使用注意:
1、对于多个字段,系统验证的顺序好像和字段声明的顺序是不一样的,好像是无须的
2、对于每个验证如果没有message属性,系统会使用默认的,如对应@NotBlank相当于@NotBlank(message=”不能为空”)
3、对于引用类型如String等类型,一定要结合@NotNull、@NotEmpty或者@NotBlank来配合使用,如果不写空的约束,经测试,该字段是不参与校验的,如单独使用@Pattern、@Length、@Email等是不会进行验证的,必须要使用空的约束来限制
@Min:用于基本数据类型,如int、long等
@NotNull: 任何对象的value不能为null
@NotEmpty:集合对象的元素不为0,即集合不为空,也可以用于字符串不为null
@NotBlank:只能用于字符串不为null,并且字符串trim()以后length要大于0
三:分组校验@Validated
- @Valid是属于javax.validation.Valid里的。
- @Validated是@Valid 的一次封装,是spring提供的校验机制使用(org.springframework.validation.annotation.Validated) ,@Valid不提供分组功能
1. 分组的作用(使用场景):
每个校验的注解约束都有一个groups属性,用于指定该约束是属于哪个组的,这样在同一个字段上就可以配置多套约束,在使用的时候只需要指定使用那套约束即可,例如对于注册用户和修改用户信息时,注册时id必须为空,修改用户信息时id必须不能为空,在使用的时候只需要将这两种约束分配到不同的组中即可,如添加时使用组A的约束,更新时使用组B的约束
2. 分组就是一个空接口interface
GroupA 和 GroupB
package com.mengdee.manage.validator; public interface GroupA { } package com.mengdee.manage.validator; public interface GroupB { }
在使用时指定具体使用那套分组的约束@Validated({GroupA.class})
3. 组序列@GroupSequence
默认情况下,不同组别的约束验证是无序的,组序列就是按照分组的前后顺序依次验证,如先验证GroupA组的约束,再验证GroupB组的约束。如果对组的校验顺序有要求,例如必须先校验A组再校验B组,可以使用@GroupSequence来定义每个组的顺序
使用场景:
(1)第二个组中的约束验证依赖于一个稳定状态来运行,而这个稳定状态是由第一个组来进行验证的。
(2)某个组的验证比较耗时,CPU 和内存的使用率相对比较大,最优的选择是将其放在最后进行验证。因此,在进行组验证的时候尚需提供一种有序的验证方式,这就提出了组序列的概念。
一个组可以定义为其他组的序列,使用它进行验证的时候必须符合该序列规定的顺序。在使用组序列验证的时候,如果序列前边的组验证失败,则后面的组将不再给予验证。
使用注解GroupSequence定义组序列:GroupAB
package com.mengdee.manage.validator; import javax.validation.GroupSequence; @GroupSequence({GroupA.class, GroupB.class}) public interface GroupAB {
ValidationController
package com.mengdee.manage.controller; import javax.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import com.mengdee.manage.validator.GroupA; import com.mengdee.manage.validator.GroupAB; import com.mengdee.manage.validator.GroupB; import com.mengdee.manage.validator.UserEntity; @Controller @RequestMapping("/validated") public class ValidationController extends BaseController { // 校验时指定了GroupA,那么只校验约束中包含GroupA的约束,没有包含就不校验,例如对于phone, @NotBlank指定的分组,而@Pattern没有指定分组,那么只校验空着一个约束,不校验手机号格式 // http://localhost:8081/platform-springmvc-webapp/validated/groupA?password=root123&phone=123 @RequestMapping(value = "/groupA", method = RequestMethod.GET) public @ResponseBody Object groupA(HttpServletRequest request, @Validated({GroupA.class}) UserEntity user, BindingResult bindingResult){ if (bindingResult.hasErrors()) { FieldError fieldError = bindingResult.getFieldError(); return this.ResponseJsonError(fieldError); } return "ok"; } // http://localhost:8081/platform-springmvc-webapp/validated/groupB?phone=123&password=root123&id=1 @RequestMapping(value = "/groupB", method = RequestMethod.GET) public @ResponseBody Object groupB(HttpServletRequest request, @Validated({GroupB.class}) UserEntity user, BindingResult bindingResult){ if (bindingResult.hasErrors()) { FieldError fieldError = bindingResult.getFieldError(); return this.ResponseJsonError(fieldError); } return "ok"; } // groupAB // http://localhost:8081/platform-springmvc-webapp/validated/groupAB?phone=111&password=root123&nickname=123&email=xxx@qq.com // @Validated({GroupA.class, GroupB.class}):GroupA和GroupB的关系是或的关系,就像数据库中的OR一样,只要满足一个条件就会对该约束进行校验,同时使用多个组注意多个组之间没有先后属性之说,并不是先校验组A,然后再校验组B // 因为id的为空和不为空的约束都会进行检查,所以先注释掉该属性 @RequestMapping(value = "/groupAB", method = RequestMethod.GET) public @ResponseBody Object groupAB(HttpServletRequest request, @Validated({GroupA.class, GroupB.class}) UserEntity user, BindingResult bindingResult){ if (bindingResult.hasErrors()) { FieldError fieldError = bindingResult.getFieldError(); return this.ResponseJsonError(fieldError); } return "ok"; } // default // http://localhost:8081/platform-springmvc-webapp/default?email=xxx@163.com&age=18 // @Validated 如果没有指定groups则验证没有分组的属性(此时和@Valid功能一样),如果一个字段上有多个约束,都必须没有指定组,如果部分约束指定的组,部分约束没有指定约束,那么在使用@Validated时不进行检查的 @RequestMapping(value = "/default", method = RequestMethod.GET) public @ResponseBody Object defaultGroup(HttpServletRequest request, @Validated UserEntity user, BindingResult bindingResult){ if (bindingResult.hasErrors()) { FieldError fieldError = bindingResult.getFieldError(); return this.ResponseJsonError(fieldError); } return "ok"; } //localhost:8081/platform-springmvc-webapp/validated/sequence?phone=123&password=root123&email=123&nickname=123 // 对于一个属性上有多个约束,并且多个约束不都在同一个组,那么在检查的时候顺序是根据GroupSequence定义的先后顺序来检查的 @RequestMapping(value = "/sequence", method = RequestMethod.GET) public @ResponseBody Object sequence(HttpServletRequest request, @Validated({GroupAB.class}) UserEntity user, BindingResult bindingResult){ if (bindingResult.hasErrors()) { FieldError fieldError = bindingResult.getFieldError(); return this.ResponseJsonError(fieldError); } return "ok"; } }
四:自定义hibernate validation注解
当hibernate validation提供的注解不能满足需求时,可以自定义校验约束。
自定义注解约束步骤:
- 创建注解
- 创建注解对应的约束验证类
- 使用注解
- 测试注解
创建注解 @Phone
package com.mengdee.manage.validator; import java.lang.annotation.Target; import javax.validation.Constraint; import javax.validation.Payload; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) @Documented @Constraint(validatedBy = {PhoneConstraint.class}) public @interface Phone { String message() default "手机号格式错误"; String regexp() default "^((13[0-9])|(15[^4,\\D])|(18[0,3-9]))\\d{8}$"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default { }; @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) @Documented public @interface List { Phone[] value(); } }
创建手机号注解对应的约束验证类PhoneConstraint
package com.mengdee.manage.validator; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class PhoneConstraint implements ConstraintValidator<Phone, String> { private String regexp; @Override public void initialize(Phone phoneAnnotation) { this.regexp = phoneAnnotation.regexp(); } @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null) { return true; // HV000028: Unexpected exception during isValid call } if (value.matches(regexp)) { return true; } return false; } }
在属性上使用注解@Phone
package com.mengdee.manage.validator; import org.hibernate.validator.constraints.NotBlank; public class UserDetail { @NotBlank @Phone private String phone; public UserDetail() { } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } }
测试注解
// http://localhost:8081/platform-springmvc-webapp/Valid/test3?address=123&company=456&phone=123 @RequestMapping(value = "/test3", method = RequestMethod.GET) public @ResponseBody Object validationCustomAnnotation(HttpServletRequest request, @Valid UserDetail userDetail, BindingResult bindingResult){ if (bindingResult.hasErrors()) { FieldError fieldError = bindingResult.getFieldError(); return super.ResponseJsonError(fieldError); } return "ok"; }
完整代码下载:http://xiazai.jb51.net/201804/yuanma/platform-springmvc-webapp(jb51.net).rar
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。
参考文章:
- hibernate validation文档:http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html_single/#validator-customconstraints:
- 自定义注解:https://www.jb51.net/article/139111.htm
- 深入理解Java:注解(Annotation)自定义注解入门:https://www.jb51.net/article/35949.htm