目前在软件开发过程当中 , 先后端基本是经过数据接口方式进行交互 . 做为一名后端程序员 , 对前端的同事提交数据的合法性进行校验俨然是一个必不可少的环节 .前端
而Spring Boot 或者 Spring MVC 中已经集成了 用于 校验JSR 303 规范 (Bean Validation 1.0) 的jar包 , 咱们内置许多事先定义好的validator , 让咱们仅经过注解的方式即可以对接口参数进行校验 .java
这里以一个登陆接口为例 :git
UserLoginDTO :程序员
@Data
@EqualsAndHashCode(callSuper = false)
public class UserLoginDTO implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank(message = "帐号不能为空")
@Size(min = 6, max = 10, message = "帐号长度在6-10位之间")
private String account;
@NotBlank(message = "密码不能为空")
private String password;
}
复制代码
接口 :正则表达式
/** * 登陆接口 */
@PostMapping
public LoginSuccessVO login(@RequestBody @Valid UserLoginDTO loginDTO) {
return loginService.login(loginDTO);
}
复制代码
这里只须要在DTO的字段上加上validation包的注解 , 一个字段能够被多个注解所标记 , 例如示例中的@NotBlank
, @Size
. 而后在做为controller方法的入参处使用@Valid
注解进行标记便可 . 如上所示 , 这样当请求进入到controller方法内部前 , 就会对前端传过来的参数进行校验判断 . 若校验不经过 , 则便抛出异常 , 且message为注解中message
属性指定的消息 .后端
validation 包中可用的注解以下图所示 :markdown
其中各个注解的做用能够参考下表 :app
注解 | 做用 |
---|---|
@Null | 被标记的字段必须为null |
@NotNull | 被标记的字段必须不为null (没法检查长度为0的字符串) |
@NotBlank | 被标记的字符串被trim后的长度不能为0 |
@NotEmpty | 被标记的字段不能为null 或者 empty (适用于String , Collection , Map , Array) |
注解 | 做用 |
---|---|
@Size(min=?, max=?) | 被标记的字段的长度大小必需要在min 与max 指定的范围内 (适用于String , Collection , Map , Array) |
@Length(min=?, max=?) | 被标记的字符串长度必需要为在min 与max 指定的范围内 |
下表注解支持的日期类型包括 java 8 之前的 Date
对象 , 以及 java 8的LocalDateTime
系列 . 具体能够参考注解的java doc 注释 .ide
注解 | 做用 |
---|---|
@Past | 被标记的日期字段必须在当前时间以前 |
@PastOrPresent | 被标记的日期字段必须在当前时间以前或等于当前时间 |
@Future | 被标记的日期字段必须在当前时间以后 |
@FutureOrPresent | 被标记的日期字段必须在当前时间或等于当前时间 |
注解 | 做用 |
---|---|
@AssertFalse | 被标记的字段必需要为false |
@AssertTrue | 被标记的字段必需要为true |
下表注解当被标记的字段为null
时 , 会经过校验 , 因此通常搭配@NotNull
进行使用 .oop
注解 | 做用 |
---|---|
@Min(value=?) | 被标记的字段必需要大于value 值(适用于BigDecimal , BigInteger , byte , short , int , long 以及他们的包装类型) |
@Max(value=?) | 被标记的字段必需要小于value 值(适用于BigDecimal , BigInteger , byte , short , int , long 以及他们的包装类型) |
@DecimalMax(value=? ,inclusive=true ) | 被标记的字段必需要小于(等于)value 值 , inclusive 为true 表示等于value 值也符合条件 , 反之则不符合 . (适用于BigDecimal , BigInteger ,String , byte , short , int , long 以及他们的包装类型) |
@DecimalMin(value=? ,inclusive=true ) | 被标记的字段必需要大于(等于)value 值 , inclusive 为true 表示等于value 值也符合条件 , 反之则不符合 .(适用于BigDecimal , BigInteger ,String , byte , short , int , long 以及他们的包装类型) |
@Digits(integer=?,fraction=?) | 被标记的字段必需要为符合指定格式的数字,interger指定整数精度,fraction指定小数精度。 |
@Range(min=?, max=?) | 被标记的字段必须在min 与max 范围内 , (适用于BigDecimal , BigInteger , byte , short , int , long 以及他们的包装类型) |
@Positive | 被标记的字段必须为正数 (适用于BigDecimal , BigInteger ,String , byte , short , int , long 以及他们的包装类型) |
@PositiveOrZero | 被标记的字段必须为正数或0 (适用于BigDecimal , BigInteger ,String , byte , short , int , long 以及他们的包装类型) |
@Negative | 被标记的字段必须为负数 (适用于BigDecimal , BigInteger ,String , byte , short , int , long 以及他们的包装类型) |
@NegativeOrZero | 被标记的字段必须为负数或0 (适用于BigDecimal , BigInteger ,String , byte , short , int , long 以及他们的包装类型) |
注解 | 做用 |
---|---|
被标记的字段必需要为邮件地址格式 , 若是是null , 会算做经过校验 , 通常与@NotNull 搭配使用 (适用于字符串) |
|
@Pattern(regexp=?) | 被标记的字段必需要符合regexp 指定的正则表达式 (适用于字符串) |
单凭一个注解确定没法进行对字段进行校验 , 阅读文档可知 . 注解的校验逻辑均编写在对应的Validator类中 , 这里以@NotNull
为例 . 其校验注解对应的validator以下所示 :
/** * Validate that the object is not {@code null}. * * @author Emmanuel Bernard */
public class NotNullValidator implements ConstraintValidator<NotNull, Object> {
@Override
public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
return object != null;
}
}
复制代码
能够看到 , 上面@NotNull
的validator 实现了一个叫ConstraintValidator
的接口 . 以下所示 :
/** * <A> 为注解类型 * <T> 为被校验的字段类型 */
public interface ConstraintValidator<A extends Annotation, T> {
/** * 该方法用于读取注解中传递的值 , 初始化到Validator中 . */
default void initialize(A constraintAnnotation) {
}
/** * 该方法用于编写校验逻辑,返回true表示校验经过 , false表示不经过 . */
boolean isValid(T value, ConstraintValidatorContext context);
}
复制代码
同理 , 若是咱们想实现本身的jsr-303规范的校验注解 , 自定义一个注解并实现一个validator便可 . 这里我编写一个用于校验身份证格式的注解 . 以下所示 :
/** * @author wukun * @since 2019/11/26 */
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IdCardValidator.class}) // 必须使用 @Constraint注解标记自定义注解, 并在validatedBy中指定用于校验的validator类型 , 可指定多个.
public @interface IdCard {
// 是否必传,默认必传. (该字段为自定义字段)
boolean required() default true;
// 如下三个字段为必备字段,直接复制过来便可 .
String message() default "IdCard format is invalid!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
复制代码
/** * @author wukun * @since 2019/11/26 */
public class IdCardValidator implements ConstraintValidator<IdCard, String> {
//用于接收注解上自定义的 required
private boolean required;
@Override
public void initialize(TodayOrAfter constraintAnnotation) {
required = constraintAnnotation.required();
}
@Override
public boolean isValid(LocalDateTime value, ConstraintValidatorContext context) {
if (Objects.isNull(value) && required) {
return false;
} else {
//此处编写校验逻辑 ....
}
}
}
复制代码
到此 , 自定义jsr 303 注解便编写完成了 .