简介
JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation。
在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情。应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的。在通常的情况下,应用程序是分层的,不同的层由不同的开发人员来完成。很多时候同样的数据验证逻辑会出现在不同的层,这样就会导致代码冗余和一些管理的问题,比如说语义的一致性等。为了避免这样的情况发生,最好是将验证逻辑与相应的域模型进行绑定。
Bean Validation 为 JavaBean 验证定义了相应的元数据模型和 API。缺省的元数据是 Java Annotations,通过使用 XML 可以对原有的元数据信息进行覆盖和扩展。在应用程序中,通过使用 Bean Validation 或是你自己定义的 constraint,例如 @NotNull, @Max, @ZipCode, 就可以确保数据模型(JavaBean)的正确性。constraint 可以附加到字段,getter 方法,类或者接口上面。对于一些特定的需求,用户可以很容易的开发定制化的 constraint。Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。
Bean Validation 规范内嵌的约束注解
实例
基本应用
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
给参数对象添加校验注解
@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()) { Map<String , String> map = 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(); } Map<String,String> errorMap = 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 和 payload) 之外, 我们还添 加了一个属性用来指定所要求的值. 此属性的名称vals在annotation的定义中比较特 殊, 如果只有这个属性被赋值了的话, 那么, 在使用此annotation到时候可以忽略此属性名称.
另外, 我们还给这个annotation标注了一些元标注( meta annotatioins):
创建约束校验器
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.HashSet; import java.util.Set; public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> { private Set<Integer> 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; }
测试如下:
源码地址
https://github.com/gf-huanchupk/SpringBootLearning
参考
总结
到此这篇关于Spring Boot利用JSR303实现参数验证的文章就介绍到这了,更多相关Spring Boot用JSR303参数验证内容请搜索小牛知识库以前的文章或继续浏览下面的相关文章希望大家以后多多支持小牛知识库!
问题内容: 如果参数无效,如何使用JSR-303验证方法参数并引发异常? 例如像这样:? 现在,我检查方法主体中的每个方法参数,例如 而且我觉得这很丑。 PS作为参考实现,我使用Hibernate-validator 4.1.0.Final 问题答案: 如果可以升级到Hibernate Validator 4.2.0或更高版本,则可以使用其方法验证功能,该功能为方法参数和返回值的验证提供支持。 可
本文向大家介绍利用layer实现表单完美验证的方法,包括了利用layer实现表单完美验证的方法的使用技巧和注意事项,需要的朋友参考一下 如下所示: 上边是封装好的layer方法,下边是结合表单开始验证 在ajax提交表单之前调checkForm方法即可,如: 以上这篇利用layer实现表单完美验证的方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持呐喊教程。
本文向大家介绍SpringBoot使用validation-api实现参数校验的示例,包括了SpringBoot使用validation-api实现参数校验的示例的使用技巧和注意事项,需要的朋友参考一下 我们在开发Java项目的时候,经常需要对参数进行一些必填项、格式、长度等进行校验,如果手写代码对参数校验,每个接口会需要很多低级的代码,这样会降低代码的可读性。那么我们能不能使用一种比较优雅的方式
本文向大家介绍Springboot实现验证码登录,包括了Springboot实现验证码登录的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了Springboot实现验证码登录的具体代码,供大家参考,具体内容如下 因为在项目中需要使用到验证码,我总结一下在项目中如何快速解决项目需求~验证码,下面推荐给大家速上手验证码的例子。 一、编写验证码工具类 二、controller层使用 验证用户
本文向大家介绍利用PHP绘图函数实现简单验证码功能的方法,包括了利用PHP绘图函数实现简单验证码功能的方法的使用技巧和注意事项,需要的朋友参考一下 index.php login.php 以上就是小编为大家带来的利用PHP绘图函数实现简单验证码功能的方法全部内容了,希望大家多多支持呐喊教程~
本文向大家介绍SpringBoot实现短信验证码校验方法思路详解,包括了SpringBoot实现短信验证码校验方法思路详解的使用技巧和注意事项,需要的朋友参考一下 有关阿里云通信短信服务验证码的发送,请参考我的另一篇文章 Springboot实现阿里云通信短信服务有关短信验证码的发送功能 思路 用户输入手机号后,点击按钮获取验证码。并设置冷却时间,防止用户频繁点击。 后台生成验证码并发送到用户