目录
第二步:实现 ConstraintValidator 接口,并不是刚才定义的接口
使用初衷
如果多个表单提交,不管前端是否进行数据校验,后台接口中一定要对重要数据进行校验以及数据库字段设置,因此,如果对每个表单进行数据校验,那代码写的比较繁琐,而且controller层一长串都是数据校验,如果一个表单对象有三十多个属性,自己也觉得写的想吐,所以,我们使用spring自带的数据校验框架vaildation。
实现流程
我们选择一种常用的校验方式,大致流程也比较简单和实用,请求接口-->controller层-->接收对象-->对象通过注解方式进行基本的数据校验-->如果校验不通过-->通过全局异常处理进行统一返回异常
常用的校验注解
@Null | 限制只能为null |
@NotNull | 限制必须不为null |
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 | |
@AssertTrue | 限制必须为true |
@AssertFalse | 限制必须为false |
@DecimalMax(value) | 限制必须为一个不大于指定值的数字 |
@DecimalMin(value) | 限制必须为一个不小于指定值的数字 |
@Digits(integer,fraction) | 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction |
@Future | 限制必须是一个将来的日期 |
@Max(value) | 限制必须为一个不大于指定值的数字 |
@Min(value) | 限制必须为一个不小于指定值的数字 |
@Past | 限制必须是一个过去的日期 |
@Pattern(value) | 限制必须符合指定的正则表达式 |
@Size(max,min) | 限制字符长度必须在min到max之间 |
@Past | 验证注解的元素值(日期类型)比当前时间早 |
@NotEmpty | 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) |
@NotBlank | 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
代码实现:
注:RestResponse是我统一返回的对象封装
1.controller 层对象加上@valid 即可
@PostMapping("insert")
public RestResponse<?> insert(@Valid UserInfo user){
int insert = userService.insert(user);
return RestResponse.success(insert);
}
2.在你需要校验的对象属性上增加校验注解
@NotNull(message = "请填写婚姻信息")
private String marriage;
@NotNull(message = "请填写民族")
private String nation;
@NotNull(message = "请填写籍贯")
private String nativePlace;
@NotNull(message = "请填写政治面貌")
private String politic ;
@Email(message = "邮箱地址错误")
private String email ;
3.全局异常处理
/**
* 全局异常处理中心
* @author fangh
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
}
/**
* 校验错误拦截处理
* @param bindException
* @return {@link RestResponse}
*/
@ExceptionHandler(value = BindException.class)
@ResponseBody
public RestResponse<?> schedulerException(BindException bindException){
BindingResult bindingResult = bindException.getBindingResult();
if(bindingResult.hasErrors()){
List<ObjectError> errors = bindingResult.getAllErrors();
for (ObjectError objectError:errors) {
FieldError fieldError = (FieldError) objectError;
logger.error("数据校验失败 : 对象【"+fieldError.getObjectName()
+"】中的【"+fieldError.getField()
+ "】的属性校验错误,信息为:["+fieldError.getDefaultMessage()+"]");
return RestResponse.fail(Commons.SysCode.vaild.getCode(),fieldError.getDefaultMessage());
}
}
return RestResponse.fail(Commons.SysCode.vaild.getCode(),bindException.getMessage());
}
常用的属性校验数据方式(不能使用不同校验注解来实现)
第一种:借助@Pattern
直接在属性名上加入此注解,一般会和@NotNull 来配合使用,不然如果数据为空,直接跳过,当然你也可以在校验规则中加入不为空。
常用的校验规则:
校验手机号码:
@Pattern(regexp = "(^1(3|4|5|7|8)\\d{9}$)", message = "手机号码格式错误")
校验身份证号码:
@Pattern(regexp = "^(\\d{18,18}|\\d{15,15}|(\\d{17,17}[x|X]))$", message = "身份证格式错误")
第二种:借助自定义注解方式
这里我就不写简单的注解校验,写一种比较复杂的数据校验,当然这是我自己想出来的,搜了各种资料,发现没有相关的博客,有可能是我没找到,最后决定自己去实现,具体场景就是,比如一个职工,合同到期时间和系统登录时间肯定在入职时间前面,如果目前我找的资料暂时没有这种注解支持,如果有,请留言告知,谢谢!
第一步:定义一个注解 CompareDateValid
注:1.因为水准不行,但为了显示代码格调上一个层次,也就用自己英语一级水准写了注释,勿喷
2.上面相关的注解都有中文说明,不需要你们查询各种资料
package com.along.interfaces;
import com.along.interfaces.impl.CompareDateImpl;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* 日期校验
* @Documented 注解表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了
* @Retention 1.RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
* 2.RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
* 3.RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的
* 代码所读取和使用
*@Target 作用范围 1.METHOD 可用于描述方法上
* 2.TYPE 用于描述类、接口(包括注解类型)或enum声明上
* 3.ANNOTATION——TYPE 用于注解类型上(被@interface修饰的类型)
* 4.CONSTRUCTOR 用于描述构造器上
* 5.FIELD 用于描述域
* 6.LOCAL_VARIABLE 用于描述局部变量
* 7.Package 用于记录java文件的package信息
* 8.PARAMETER 可用于参数上
* @Constraint 指定了需要进行校验的策略类集合(即接口实现对应的类,实现的类必须要实现 ConstraintValidator 这个接口,并实现里面的
* initialize和isValid方法),具体实现请看 {@link com.along.interfaces.impl.CompareDateImpl}
* @author fangh
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Constraint(validatedBy = {CompareDateImpl.class})
public @interface CompareDateValid {
/**
* It is must be to required
* default false ,you can change data
* allowed value : true , false
*/
boolean required() default false;
/**
* It was error to show message when valited
* show message to request
* allowed value : anything
*/
String message() default "日期应该在之后或者之前";
/**
*It is compare filed,you must to be write
* allowed value : the field of the same object
*/
String field() default "";
/**
* If you want to compare with filed of the same object,you can to write
* you must be write the filed of the same object,or must be cause the error
* allowed value : the filed of the same object except self
*/
String[] compareField() default {};
/**
* If comapre time of after
* defalut after of time for filed
*
*/
boolean compareAfter() default true;
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
第二步:实现 ConstraintValidator 接口,并不是刚才定义的接口
重点:1.通过反射的方式获取相对应值对象属性
2.重新定义新的信息模板,返回更改后的message值
package com.along.interfaces.impl;
import com.along.interfaces.CompareDateValid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
/**
* 日期校验
* @author fangh
*/
public class CompareDateImpl implements ConstraintValidator<CompareDateValid,Object> {
private static final Logger logger = LoggerFactory.getLogger(CompareDateImpl.class);
private boolean required ;
private String message;
private String[] compareField;
private String field;
private boolean compareAfter;
@Override
public void initialize(CompareDateValid constraintAnnotation) {
this.required = constraintAnnotation.required();
this.message = constraintAnnotation.message();
this.compareField = constraintAnnotation.compareField();
this.field = constraintAnnotation.field();
this.compareAfter = constraintAnnotation.compareAfter();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if(!required){
if(value == null){
return true;
}
}
try {
//属性获取get方法属性,并获取对应的值
Object invokeValue = getValueByGetMethod(value, field);
if(invokeValue == null){
return false;
}
Date fieldDate = (Date) invokeValue;
//比较属性集合中的get方法,并获取相对应的值,且进行常规比较
if(compareField != null){
for (String compareFields:compareField) {
Object compareInvokeValue = getValueByGetMethod(value, compareFields);
if(compareInvokeValue == null){
return false;
}
Date compareDate = (Date) compareInvokeValue;
this.message =field+"应该在"+compareFields+(compareAfter?"之后":"之前");
if(fieldDate.after(compareDate) != compareAfter){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addPropertyNode(field).addConstraintViolation();
return false ;
}
}
}
return true ;
} catch (NoSuchMethodException e) {
logger.error(e.getMessage());
this.message = "请填写正确属性名称" ;
} catch (IllegalAccessException e) {
logger.error(e.getMessage());
this.message = "请在拥有访问权限的类上加入CompareDate注解" ;
} catch (InvocationTargetException e) {
logger.error(e.getMessage());
this.message = "注解所在的类get方法执行异常";
}catch (ClassCastException e){
logger.error(e.getMessage());
this.message = "请填写正确的日期格式";
}
return false;
}
package com.along.interfaces.impl;
import com.along.interfaces.CompareDateValid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
/**
* 日期校验
* @author fangh
*/
public class CompareDateImpl implements ConstraintValidator<CompareDateValid,Object> {
private static final Logger logger = LoggerFactory.getLogger(CompareDateImpl.class);
private boolean required ;
private String message;
private String[] compareField;
private String field;
private boolean compareAfter;
@Override
public void initialize(CompareDateValid constraintAnnotation) {
this.required = constraintAnnotation.required();
this.message = constraintAnnotation.message();
this.compareField = constraintAnnotation.compareField();
this.field = constraintAnnotation.field();
this.compareAfter = constraintAnnotation.compareAfter();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if(!required){
if(value == null){
return true;
}
}
try {
//属性获取get方法属性,并获取对应的值
Object invokeValue = getValueByGetMethod(value, field);
if(invokeValue == null){
return false;
}
Date fieldDate = (Date) invokeValue;
//比较属性集合中的get方法,并获取相对应的值,且进行常规比较
if(compareField != null){
for (String compareFields:compareField) {
Object compareInvokeValue = getValueByGetMethod(value, compareFields);
if(compareInvokeValue == null){
return false;
}
Date compareDate = (Date) compareInvokeValue;
this.message =field+"应该在"+compareFields+(compareAfter?"之后":"之前");
if(fieldDate.after(compareDate) != compareAfter){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addPropertyNode(field).addConstraintViolation();
return false ;
}
}
}
return true ;
} catch (NoSuchMethodException e) {
logger.error(e.getMessage());
this.message = "请填写正确属性名称" ;
} catch (IllegalAccessException e) {
logger.error(e.getMessage());
this.message = "请在拥有访问权限的类上加入CompareDate注解" ;
} catch (InvocationTargetException e) {
logger.error(e.getMessage());
this.message = "注解所在的类get方法执行异常";
}catch (ClassCastException e){
logger.error(e.getMessage());
this.message = "请填写正确的日期格式";
}
return false;
}
/**
* 通过反射中get方法获取对象中的属性值
* @param obj 对象
* @param field 对象中的属性名称
* @return 对象属性中的值
* @throws InvocationTargetException 执行方法异常
* @throws IllegalAccessException 访问权限异常
* @throws NoSuchMethodException 没有对应的方法异常
*/
public static Object getValueByGetMethod(Object obj,String field) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
String getMethod = "get"+field.substring(0, 1).toUpperCase()+field.substring(1, field.length());
Method method = obj.getClass().getMethod(getMethod, new Class[]{});
return method.invoke(obj, new Object[]{});
}
}
}
第三步:在对象上使用自定义注解
@CompareDateValid(field = "contractExpireTime",compareField = {"hireTime"},required = false,compareAfter = true)
完成,请求接口你就可以开始测试了。
喜欢我的朋友,请点个赞,我会带来更多的容易理解的博客给各位!