# 校验
# spring注解校验
# 注解校验国际化
@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;
}
}
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>
2
3
4
5
- JSR303规范中,注解必须要有三个属性:
// 默认错误提示信息从哪里获取,这里是从ValidationMessages.properties配置文件中获取
String message() default "{javax.validation.constraints.Min.message}";
// 分组
Class<?>[] groups() default {};
// 自定义信息
Class<? extends Payload>[] payload() default {};
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 = {}
)
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 {};
}
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
- 自定义校验器:
/**
* 自定义校验器,必须实现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);
}
}
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
| 注解 | 释义 |
|---|---|
| @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) | 被注释的元素必须符合指定的正则表达式。 |
| 被注释的元素必须是电子邮件地址 | |
| @Length | 被注释的字符串的大小必须在指定的范围内 |
| @Range | 被注释的元素必须在合适的范围内 |
# Spring5中的校验注解
新增注解
从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;
2
3
4
5
6
- @Nullable
使用在字段,方法参数或方法的返回值。表示可以为空。
当一个类的包被@NonNullFields或@NonNullApi注解,而我们想要从包级别指定的非null约束中免除某些字段,方法,返回值时可以使用@Nullable
- @NonNullApi
和@NonNullFields一样使用在包级别,但是区别是它作用是该包下的类的方法参数和返回值不能为空
当一个类中的方法参数和返回值使用了太多的NonNull时可以考虑使用@NonNullFields注解,使用该注解必须先定义一个名为package-info.java的文件,形式同上。
# 分组校验
- vo 页面传过来的数据进行校验
inferface : 只是作为标记一个组别 可以在vo验证的某个字段上面加入多个组别,这样没有加入的组别就不会验证这个字段
controller: 需要 加入 @Validated (ValidationGroup1.class) //ValidationGroup1.class是定义的分组;
ValidationGroup2.class 需要校验的字段是不会验证的
public interface ValidationGroup1 {
}
public interface ValidationGroup2 {
}
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;
}
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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 直接校验
@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;
}
}
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());
}
}
2
3
4
5
6
7
8
9
10
必须在校验的参数前加上@Valid或者@Validated才会进行校验,且必须写在参数前面,不可写在方法上面
# Validated Valid
分组
@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;
2
3
4
5
6
7