# 校验

back

# spring注解校验

# 注解校验国际化

back

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        // 这个拦截器会拦截请求中 key 为 lang 的参数(不配置的话是 locale),这个参数则指定了当前的环境信息。
        interceptor.setParamName("lang");
        registry.addInterceptor(interceptor);
    }
    @Bean
    LocaleResolver localeResolver() {
        // 提供了一个 SessionLocaleResolver 实例,这个实例会替换掉默认的 AcceptHeaderLocaleResolver,不同于 AcceptHeaderLocaleResolver 通过请求头来判断当前的环境信息,SessionLocaleResolver 将客户端的 Locale 保存到 HttpSession 对象中,并且可以进行修改(这意味着当前环境信息,前端给浏览器发送一次即可记住,只要 session 有效,浏览器就不必再次告诉服务端当前的环境信息)。
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return localeResolver;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

ValidationMessages

springboot默认是将国际化文件命名为ValidationMessages例如ValidationMessages_zh_CN.properties.

ValidationMessages_zh_TW.properties.国际化配置文件必须放在classpath的根目录下,即src/java/resources的根目录下。

若要自定义文件位置或名称则需要重写WebMvcConfigurerAdapter 的 getValidator 方法,但WebMvcConfigurerAdapter在springboot2中已经废弃了,可以改为使用WebMvcConfigurationSupport

# 自定义注解校验

# 自定义注解校验引入pom

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
1
2
3
4
5
  • JSR303规范中,注解必须要有三个属性:
// 默认错误提示信息从哪里获取,这里是从ValidationMessages.properties配置文件中获取
String message() default "{javax.validation.constraints.Min.message}";
// 分组
Class<?>[] groups() default {};
// 自定义信息
Class<? extends Payload>[] payload() default {};
1
2
3
4
5
6
  • 必须要有如下元数据信息:
// 注解可以作用在哪些位置
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
// 该注解可以在运行时获取到
@Retention(RUNTIME)
@Documented
// 使用哪个校验器
@Constraint(
    validatedBy = {}
)
1
2
3
4
5
6
7
8
9
  • 自定义校验注解:
@Documented
// 使用自定义校验器
@Constraint(
        validatedBy = {ListValueConstraintValidator.class}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
    // 指定在配置文件中取com.atguigu.common.valid.ListValue.message信息
    String message() default "{com.atguigu.common.valid.ListValue.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

// 自定义添加一个vals属性
    int[] vals() default {};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • 自定义配置文件 resources/ValidationMessages.properties:
com.atguigu.common.valid.ListValue.message=the specified value must be submitted
1
  • 自定义校验器:
/**
 * 自定义校验器,必须实现ConstraintValidator接口
 * ConstraintValidator接口后的泛型,第一个参数指定注解,第二个参数表示校验什么类型的数据
 * 这是校验的是Integer字段类型,如果需要校验其他类型,需要再写一个校验器,然后用
 * @Constraint注解的validatedBy 属性指定多个校验器
 */
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
    private Set<Integer> set = new HashSet<>();

    /**
     * 初始化方法
     * @param constraintAnnotation 注解下的属性详细信息
     */
    @Override
    public void initialize(ListValue constraintAnnotation) {
        // 案例中指定showStatus字段必须取值为0或者1,所以这个数组vals就是[0, 1]
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }
    }

    /**
     * 判断是否检验成功
     * @param value 提交给这个注解下属性字段的值(也就是提交过来的showStatus值)
     * @param constraintValidatorContext
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
        // value的值是否存在set集合中
        return set.contains(value);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

back

注解 释义
@Null 被注释的元素必须为null
@NotNull 被注释的元素不能为null
@NotEmpty 被注释的元素不能为Empty
@NotBlank 被注释的元素不能为Blank
@AssertTrue 被注释的元素必须为true
@AssertFalse 被注释的元素必须为false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max,min) 被注释的元素的大小必须在指定的范围内。
@Digits(integer,fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Positive The annotated element must be a strictly positive number
@Past 被注释的元素必须是一个过去的日期
@PastOrPresent The annotated element must be an instant, date or time in the past or in the present
@Future 被注释的元素必须是一个将来的日期
@FutureOrPresent The annotated element must be an instant, date or time in the present or in the future
@Pattern(value) 被注释的元素必须符合指定的正则表达式。
@Email 被注释的元素必须是电子邮件地址
@Length 被注释的字符串的大小必须在指定的范围内
@Range 被注释的元素必须在合适的范围内

# Spring5中的校验注解

back

新增注解

从spring5开始新增了null-safety注解@NonNull,@Nullable,@NonNullFields,@NonNullApi,来防止出现运行时的空指针异常。

  • @NonNull

使用在字段,方法参数或方法的返回值。表示不能为空

  • @NonNullFields

使用在包级别,并且是该包下类的字段不能为空。
当一个类中的字段使用了太多的NonNull时可以考虑使用@NonNullFields注解,使用该注解必须先定义一个名为package-info.java的文件,例如:

@NonNullApi
@NonNullFields
package org.springframework.mail;

import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;
1
2
3
4
5
6
  • @Nullable

使用在字段,方法参数或方法的返回值。表示可以为空。

当一个类的包被@NonNullFields或@NonNullApi注解,而我们想要从包级别指定的非null约束中免除某些字段,方法,返回值时可以使用@Nullable

  • @NonNullApi

@NonNullFields一样使用在包级别,但是区别是它作用是该包下的类的方法参数和返回值不能为空
当一个类中的方法参数和返回值使用了太多的NonNull时可以考虑使用@NonNullFields注解,使用该注解必须先定义一个名为package-info.java的文件,形式同上。

# 分组校验

back

  • vo 页面传过来的数据进行校验

inferface : 只是作为标记一个组别 可以在vo验证的某个字段上面加入多个组别,这样没有加入的组别就不会验证这个字段
controller: 需要 加入 @Validated (ValidationGroup1.class) //ValidationGroup1.class是定义的分组;
ValidationGroup2.class 需要校验的字段是不会验证的

public interface ValidationGroup1 {
}


public interface ValidationGroup2 {
}
1
2
3
4
5
6
public class User {

    private Integer id;

    //groups属性,表示该校验属性规则所属的分组

    @Size(min = 5, max = 10, message = "{user.name.size}", groups = ValidationGroup1.class)
    private String name;

    @NotNull(message = "{user.address.notnull}", groups = ValidationGroup2.class)
    private String address;

    @DecimalMin(value = "1", message = "{user.age.size}")
    @DecimalMax(value = "200", message = "{user.age.size}")
    private Integer age;

    @Email(message = "{user.email.pattern}")
    @NotNull(message = "{user.email.notnull}", groups = {ValidationGroup1.class, ValidationGroup2.class})
    private String email;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
public class UserController {

    
    //@Validated(ValidationGroup2.class) 表示这里的校验使用ValidationGroup2分组的校验规则,即只校验邮箱地址是否为空、用户地址是否为空
    @PostMapping("/user")
    public List<String> addUser(@Validated(ValidationGroup2.class) User user, BindingResult result){
        List<String> errors = new ArrayList<>();
        if(result.hasErrors()){
            List<ObjectError> allErrors = result.getAllErrors();
            for(ObjectError error : allErrors){
                errors.add(error.getDefaultMessage());
            }
        }
        return errors;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 直接校验

back

@Getter
@Setter
@ToString
@Document
public class PersonModel extends BaseEntity implements Serializable  {
    private static final long serialVersionUID = -3500393055197611669L;
    @NotNull(message = "性别不可为空")
    private String sex;
    private String firstName;
    @NotNull(message = "不能没有姓")
    private String lastName;
    @Min(value = 12,message = "不可小于12岁")
    private Integer age;
    @NotNull(message = "人不可无国")
    private String country;

    public PersonModel(){
        super();
    }
    public PersonModel(String sex, Integer age,String lastName) {
        this.sex = sex;
        this.age = age;
        this.lastName = lastName;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@PostMapping("/addOrEdit")
    public CustomResult saveOrUpdate(HttpServletRequest request
            ,@Valid PersonModel personModel){
        try {
            return CustomResult.success(this.personModelService.saveOrUpdate(personModel));
        } catch (Exception e) {
            log.error("新增失败",e);
            return CustomResult.fail(e.getMessage());
        }
    }
1
2
3
4
5
6
7
8
9
10

必须在校验的参数前加上@Valid或者@Validated才会进行校验,且必须写在参数前面,不可写在方法上面

# Validated Valid

back

  • 分组
    @Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制。
    @Valid:作为标准JSR-303规范,还没有吸收分组的功能

  • 注解地方
    @Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
    @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上

两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。

  • 嵌套验证
    @Validated和@Valid加在方法参数前,都不会自动对参数进行嵌套验证

  • 用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证

  • @Valid: 用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。

TIP

需要实现嵌套验证,方法入参必须有@Validated或者@Valid
成员属性必须有@Valid

/**
    * 设备上报数据
    * 加入@valid以作嵌套校验
    */
@Valid
@NotNull(message = "{common.device.data.notBlank}")
private ApiDataVo deviceData;
1
2
3
4
5
6
7