今天就跟大家聊聊有关Spring Boot 中怎么利用JSR303 实现参数验证,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
让客户满意是我们工作的目标,不断超越客户的期望值来自于我们对这个行业的热爱。我们立志把好的技术通过有效、简单的方式提供给客户,将通过不懈努力成为客户在信息化领域值得信任、有价值的长期合作伙伴,公司提供的服务项目有:国际域名空间、网页空间、营销软件、网站建设、温岭网站维护、网站推广。
引入依赖
org.springframework.boot spring-boot-starter-validation
给参数对象添加校验注解
@Data public class User { private Integer id; @NotBlank(message = "用户名不能为空") private String username; @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密码必须为8~16个字母和数字组合") private String password; @Email private String email; private Integer gender; }
Controller 中需要校验的参数Bean前添加 @Valid 开启校验功能,紧跟在校验的Bean后添加一个BindingResult,BindingResult封装了前面Bean的校验结果。
@RestController @RequestMapping("/user") public class UserController { @PostMapping("") public Result save (@Valid User user , BindingResult bindingResult) { if (bindingResult.hasErrors()) { Mapmap = new HashMap<>(); bindingResult.getFieldErrors().forEach( (item) -> { String message = item.getDefaultMessage(); String field = item.getField(); map.put( field , message ); } ); return Result.build( 400 , "非法参数 !" , map); } return Result.ok(); } }
测试如下:
参数校验不通过时,会抛出 BingBindException 异常,可以在统一异常处理中,做统一处理,这样就不用在每个需要参数校验的地方都用 BindingResult 获取校验结果了。
@Slf4j @RestControllerAdvice(basePackages = "com.itwolfed.controller") public class GlobalExceptionControllerAdvice { @ExceptionHandler(value= {MethodArgumentNotValidException.class , BindException.class}) public Result handleVaildException(Exception e){ BindingResult bindingResult = null; if (e instanceof MethodArgumentNotValidException) { bindingResult = ((MethodArgumentNotValidException)e).getBindingResult(); } else if (e instanceof BindException) { bindingResult = ((BindException)e).getBindingResult(); } MaperrorMap = new HashMap<>(16); bindingResult.getFieldErrors().forEach((fieldError)-> errorMap.put(fieldError.getField(),fieldError.getDefaultMessage()) ); return Result.build(400 , "非法参数 !" , errorMap); } }
新增和修改对于实体的校验规则是不同的,例如id是自增的时,新增时id要为空,修改则必须不为空;新增和修改,若用的恰好又是同一种实体,那就需要用到分组校验。
校验注解都有一个groups属性,可以将校验注解分组,我们看下 @NotNull的源码:
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) @Repeatable(List.class) @Documented @Constraint(validatedBy = { }) public @interface NotNull { String message() default "{javax.validation.constraints.NotNull.message}"; Class>[] groups() default { }; Class extends Payload>[] payload() default { }; @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) @Documented @interface List { NotNull[] value(); } }
从源码可以看出 groups 是一个Class>类型的数组,那么就可以创建一个Groups.
public class Groups { public interface Add{} public interface Update{} }
给参数对象的校验注解添加分组
@Data public class User { @Null(message = "新增不需要指定id" , groups = Groups.Add.class) @NotNull(message = "修改需要指定id" , groups = Groups.Update.class) private Integer id; @NotBlank(message = "用户名不能为空") @NotNull private String username; @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密码必须为8~16个字母和数字组合") private String password; @Email private String email; private Integer gender; }
Controller 中原先的 @Valid不能指定分组 ,需要替换成 @Validated
@RestController @RequestMapping("/user") public class UserController { @PostMapping("") public Result save (@Validated(Groups.Add.class) User user) { return Result.ok(); } }
测试如下:
虽然JSR303和springboot-validator 已经提供了很多校验注解,但是当面对复杂参数校验时,还是不能满足我们的要求,这时候我们就需要 自定义校验注解。
例如User中的gender,用 1代表男 2代表女,我们自定义一个校验注解 @ListValue,指定取值只能1和2。
@Documented @Constraint(validatedBy = { ListValueConstraintValidator.class }) @Target({ METHOD, FIELD, ANNOTATION_TYPE }) @Retention(RUNTIME) public @interface ListValue { String message() default ""; Class>[] groups() default { }; Class extends Payload>[] payload() default { }; int[] vals() default { }; }
一个标注(annotation) 是通过
@interface关键字来定义的. 这个标注中的属性是声明成类似方法
的样式的. 根据Bean Validation API 规范的要求:
message属性, 这个属性被用来定义默认得消息模版, 当这个约束条件被验证失败的时候,通过
此属性来输出错误信息。
groups 属性, 用于指定这个约束条件属于哪(些)个校验组. 这个的默认值必须是Class>类型数组。
payload 属性, Bean Validation API 的使用者可以通过此属性来给约束条件指定严重级别. 这个属性并不被API自身所使用。
除了这三个强制性要求的属性(message, groups 和 payload) 之外, 我们还添
加了一个属性用来指定所要求的值. 此属性的名称vals在annotation的定义中比较特
殊, 如果只有这个属性被赋值了的话, 那么, 在使用此annotation到时候可以忽略此属性名称.
另外, 我们还给这个annotation标注了一些元标注( meta
annotatioins):
@Target({ METHOD, FIELD, ANNOTATION_TYPE }): 表示此注解可以被用在方法, 字段或者
annotation声明上。
@Retention(RUNTIME): 表示这个标注信息是在运行期通过反射被读取的.
@Constraint(validatedBy = ListValueConstraintValidator.class): 指明使用哪个校验器(类) 去校验使用了此标注的元素.
@Documented: 表示在对使用了该注解的类进行javadoc操作到时候, 这个标注会被添加到
javadoc当中.
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.HashSet; import java.util.Set; public class ListValueConstraintValidator implements ConstraintValidator{ private Set set = new HashSet<>(); /** * 初始化方法 */ @Override public void initialize(ListValue constraintAnnotation) { int[] vals = constraintAnnotation.vals(); for (int val : vals) { set.add(val); } } /** * 判断是否校验成功 * * @param value 需要校验的值 * @param context * @return */ @Override public boolean isValid(Integer value, ConstraintValidatorContext context) { return set.contains(value); } }
ListValueConstraintValidator定义了两个泛型参数, 第一个是这个校验器所服务到标注类型(在我们的例子中即ListValue), 第二个这个校验器所支持到被校验元素的类型 (即Integer)。
如果一个约束标注支持多种类型的被校验元素的话, 那么需要为每个所支持的类型定义一个ConstraintValidator,并且注册到约束标注中。
这个验证器的实现就很平常了, initialize() 方法传进来一个所要验证的标注类型的实例, 在本
例中, 我们通过此实例来获取其vals属性的值,并将其保存为Set集合中供下一步使
用。
isValid()是实现真正的校验逻辑的地方, 判断一个给定的int对于
@ListValue这个约束条件来说
是否是合法的。
在参数对象中使用 @ListValue注解。
@Data public class User { @Null(message = "新增不需要指定id" , groups = Groups.Add.class) @NotNull(message = "修改需要指定id" , groups = Groups.Update.class) private Integer id; @NotBlank(message = "用户名不能为空") @NotNull private String username; @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$", message = "密码必须为8~16个字母和数字组合") private String password; @Email private String email; @ListValue( message = "性别应指定相应的值" , vals = {1,2} , groups = {Groups.Add.class , Groups.Update.class}) private Integer gender; }
测试如下:
看完上述内容,你们对Spring Boot 中怎么利用JSR303 实现参数验证有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注创新互联行业资讯频道,感谢大家的支持。