JSR303 目前最新的规范已经是 JSR380 了,也就是 Bean Validation 2.0.
参数校验是一个成熟的网站必须的功能,然而有的时候为了校验参数也要费好大的劲,免不了写很多 if-else,一点也不优雅。
上手 引入依赖 <!--SpringBootWeb 这个包里面自带了 hibernate 的校验包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
JSR 提供的注解 @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 提供的 @NotBlank(message =) 验证字符串非 null,且长度必须大于 0 @Email 被注释的元素必须是电子邮箱地址 @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内 @NotEmpty 被注释的字符串的必须非空 @Range(min=,max=,message=) 被注释的元素必须在合适的范围内
自定义校验注解 @IsMobile 注解
package top.imlgw.spike.validator;import javax.validation.Constraint;import javax.validation.Payload;import java.lang.annotation.*;@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint( validatedBy = {IsMobileValidator.class} //指定真正校验的类 ) public @interface IsMobile { boolean required () default true ; String message () default "号码格式错误" ; Class<?>[] groups() default {}; Class<? extends Payload >[] payload() default {}; }
IsMobileValidator 类
自定义的校验器要实现 ConstraintValidator 接口
package top.imlgw.spike.validator;import org.apache.commons.lang3.StringUtils;import top.imlgw.spike.utils.ValidatorUtil;import javax.validation.ConstraintValidator;import javax.validation.ConstraintValidatorContext;public class IsMobileValidator implements ConstraintValidator <IsMobile, String> { private boolean required=false ; @Override public void initialize (IsMobile constraintAnnotation) { required=constraintAnnotation.required(); } @Override public boolean isValid (String s, ConstraintValidatorContext constraintValidatorContext) { if (required){ return ValidatorUtil.isMobile(s); }else { if (StringUtils.isEmpty(s)){ return true ; }else { return ValidatorUtil.isMobile(s); } } } }
字段上加注解 package top.imlgw.spike.entity;import org.hibernate.validator.constraints.Length;import javax.validation.constraints.NotNull;public class User { @NotNull(message = "id 不能为空") private Integer id; @NotNull(message = "名字不能为空") private String name; private Integer age; @Length(min = 6,message = "密码长度至少 6 位") private String password; @Override public String toString () { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", password='" + password + '\'' + '}' ; } public User (Integer id, String name, Integer age, String password) { this .name = name; this .age = age; this .password = password; this .id=id; } public String getPassword () { return password; } public void setPassword (String password) { this .password = password; } public String getName () { return name; } public void setName (String name) { this .name = name; } public Integer getAge () { return age; } public void setAge (Integer age) { this .age = age; } public long getId () { return id; } public void setId (Integer id) { this .id = id; } }
@Valid 加 BindingResult
在 Controlle 层待校验的的参数上加上@Valid
注解,然后在后面紧跟一个 BindingResult
,校验的结果会封装在这个对象里面,BindingResult 的作用是当参数不合法时能够捕捉到错误,不会直接抛异常,感觉还是有点麻烦。
@Valid 加全局异常捕获
上面的方法如果不加后面的 BindingResult 在校验失败后就会抛一个 BindException
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors Field error in object 'user' on field 'id' : rejected value [null ]; codes [NotNull.user.id,NotNull.id,NotNull.long ,NotNull]; arguments .......
那我们就可以利用@ControllerAdvice+@ExceptionHandler
来定义一个全局的异常处理器来处理这个异常,@ExceptionHandle
,针对的仅仅是单个 controller,加上@ControllerAdvice
就可以对所有的 Controller 层异常进行捕获,这里的全局仅仅指的是 controller 层。
自定义异常 package top.imlgw.spike.exception;import top.imlgw.spike.result.CodeMsg;public class GlobalException extends RuntimeException { private static final long serialVersionUID = 1L ; private CodeMsg cm; public GlobalException (CodeMsg cm) { super (cm.toString()); this .cm = cm; } public CodeMsg getCm () { return cm; } }
全局异常处理器 @ControllerAdvice @ResponseBody public class GlobalExceptionHandler { @ExceptionHandler(value = Exception.class) public Result<String> exceptionHandle (HttpServletRequest request, Exception e) { e.printStackTrace(); if (e instanceof GlobalException) { GlobalException ex = (GlobalException) e; return Result.error(ex.getCm()); } else if (e instanceof BindException) { BindException ex = (BindException) e; List<ObjectError> errors = ex.getAllErrors(); ObjectError error = errors.get(0 ); String msg = error.getDefaultMessage(); return Result.error(CodeMsg.BIND_ERROR.fillArgs(msg)); } else { return Result.error(CodeMsg.SERVER_ERROR); } } @ExceptionHandler(value = GlobalException.class) public Result<String> GLe (HttpServletRequest request, Exception e) { System.out.println("优先处理了这个 GlobalException" ); e.printStackTrace(); GlobalException ex = (GlobalException) e; return Result.error(ex.getCm()); } }
这里也做了个小测试,可以看到我定义了两个@ExceptionHandle,一个是另一个的子类,看会先处理那个,测试后发现会先处理小异常,那个最大的异常其实相当于’’兜底’’的。其实后面我为了跟精细的处理,将绑定异常和自定义的异常分开处理了。
@ControllerAdvice :
It is typically used to define {@link ExceptionHandler @ExceptionHandler },
{@link InitBinder @InitBinder }, and {@link ModelAttribute @ModelAttribute }
methods that apply to all {@link RequestMapping @RequestMapping } methods.
@author Rossen Stoyanchev
@since 3.2
分组校验 只需要在 vo 里加上对应分组的接口然后在注解上加上就可以了
public class LoginVo { @NotNull(message = "手机号不能为空") @IsMobile(groups = Test1.class) private String mobile; @NotNull(message = "密码不能为空") @Length(min=6 ,groups = Test2.class,message = "密码长度过短") private String password; public String getMobile () { return mobile; } public void setMobile (String mobile) { this .mobile = mobile; } public String getPassword () { return password; } public void setPassword (String password) { this .password = password; } @Override public String toString () { return "LoginVo [mobile=" + mobile + ", password=" + password + "]" ; } public interface Test1 {} public interface Test2 {} }
使用时注意用@Validated
,这个其实是 Spring 对 Hibernate 的二次封装,增加了一些功能。
@RequestMapping("/jsr303-2") @ResponseBody public void testJSR (@Validated({LoginVo.Test2.class}) LoginVo vo) { }
这样在这个 Controller 里 post 密码就没有长度的限制了。
其实还有一些校验的方法,基于方法校验,基于少量参数的校验,以后用到再来记录
赞赏
感谢鼓励