1,概念
在web开发中,前端的参数校验是为了用户体验,后端的参数校验是为了安全。
参数有两种形式:
1)查询字符串参数(Query String Parameters参数)
一般用于GET请求,会以url string的形式进行传递
2)请求体参数(Request Body)
一般用于POST请求,可以使用Content-Type来指定不同参数类型
3)路径参数校验pathvariable
spring-boot可能目前并不支持对参数的验证。
2,校验注解
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
注意:单个参数校验在类上添加注解:@Validated
| 注解 | 说明 | 备注 |
|---|---|---|
| @AssertFalse | 所注解的元素必须是Boolean类型,且值为false | |
| @AssertTrue | 所注解的元素必须是Boolean类型,且值为true | |
| @DecimalMax | 所注解的元素必须是数字,且值小于等于给定的值 | DecimalMax(value=,inclusive=) 小于等于value,inclusive=true表示小于等于 |
| @DecimalMin | 所注解的元素必须是数字,且值大于等于给定的值 | |
| @Digits | 所注解的元素必须是数字,且值必须是指定的位数 | |
| @Future | 所注解的元素必须是将来某个日期 | |
| @Max | 所注解的元素必须是数字,且值小于等于给定的值 | Max(value=) |
| @Min | 所注解的元素必须是数字,且值小于等于给定的值 | |
| @Range | 所注解的元素需在指定范围区间内 | @Range(min = 1, max = 5, message = "") |
| @NotNull | 所注解的元素值不能为null | |
| @NotBlank | 所注解的元素值有内容 | 常用于String |
| @NotEmpty | 不能为null,不能为空字符串"" | 本质是CharSequence, Collection, Map, or Array的size或者length不能为0 |
| @Null | 所注解的元素值为null | |
| @Past | 所注解的元素必须是某个过去的日期 | |
| @PastOrPresent | 所注解的元素必须是过去某个或现在日期 | |
| @Pattern | 所注解的元素必须满足给定的正则表达式 | Pattern(regex=,flag=) |
| @Size | 所注解的对象必须是Array,Collection,Map,String,且长度大小需保证在给定范围之内 | @Size (min=0, max=1,message="") |
| @Length | 验证字符串长度是否在给定的范围之内 | @Length(min=0, max=1,message=“”) |
| 所注解的元素需满足Email格式 | ||
| @DateTimeFormat | 日期格式化注解,将字符串转换为日期格式进行接收 | |
| @NumberFormat | number格式化,将字符串转换为数字 |
1)@DateTimeFormat
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
- 请求非JSON数据,建议用@DateTimeFormat即可,此时不会格式化返回数据(比如get请求,当然get请求也可以请求JSON数据,只是不推荐);
- 请求JSON数据,建议用@ReqeustBody来转换数据,然后搭配局部注解@JsonFormat(会格式化返回数据)或者全局配置来修改默认的日期解析格式(默认"yyyy-MM-dd’T’HH:mm:ss.SSSX");全局配置也可以格式化返回数据,需配置builder.serializerByType
- 如果日期格式化出错,先看传来的数据是否为JSON数据(可以通过consumes来限制),然后再看有没有对于的注解或日期格式化全局配置
2)@NumberFormat
@NumberFormat(pattern="#,###")
private Integer salary;
3,单个参数校验
- 在类上添加注解:
@Validated - 在接口使用注解进行校验。
@RestController
@Validated
public class PingController {
@GetMapping("/getUser")
public String getUserStr(@NotNull(message = "name 不能为空") String name,
@Max(value = 99, message = "不能大于99岁") Integer age) {
return "name: " + name + " ,age:" + age;
}
}
4,实体类参数校验
当处理post请求或者请求参数较多的时候使用一个bean来接收参数,然后在每个需要校验的属性上使用参数校验注解。
- 然后在controller类上添加注解:@Validated;
- 在方法中对@RequestBody的参数用@Valid注解;
- 在具体的实体类使用对应的注解进行校验即可。
@Data
public class UserInfo {
@NotNull(message = "username cannot be null")
private String name;
@NotNull(message = "sex cannot be null")
private String sex;
@Max(value = 99L)
private Integer age;
}
@PostMapping("/getUser")
public String getUserStr(@RequestBody@Valid UserInfo user) {
}
5,参数分组校验
用同一个实体类去接收多个controller的参数,但是不同controller所需要的参数又有些许不同。比如有一个/setUser接口不需要id参数,而/getUser接口又需要该参数,这种时候就可以使用参数分组来实现。
- 定义表示组别的interface;
public interface GroupA {
}
- 在@Validated中指定使用哪个组;
@RestController
public class PingController {
//其中Default为javax.validation.groups中的类,表示参数类中其他没有分组的参数,如果没有,/getUser接口的参数校验就只会有标记了GroupA的参数校验生效。
@PostMapping("/getUser")
public String getUserStr(@RequestBody @Validated({GroupA.class, Default.class}) UserInfo user, BindingResult bindingResult) {
validData(bindingResult);
return "name: " + user.getName() + ", age:" + user.getAge();
}
@PostMapping("/setUser")
public String setUser(@RequestBody @Validated UserInfo user, BindingResult bindingResult) {
validData(bindingResult);
return "name: " + user.getName() + ", age:" + user.getAge();
}
- 在实体类的注解中标记这个哪个组所使用的参数;
@Data
public class UserInfo {
@NotNull( groups = {GroupA.class}, message = "id cannot be null")
private Integer id;
@NotNull(message = "username cannot be null")
private String name;
@NotNull(message = "sex cannot be null")
private String sex;
@Max(value = 99L)
private Integer age;
}
6,级联校验
当参数bean中的属性又是一个复杂数据类型或者是一个集合的时候,如果需要对其进行进一步的校验.
@Data
public class UserInfo {
@NotNull( groups = {GroupA.class}, message = "id cannot be null")
private Integer id;
@NotNull(message = "username cannot be null")
private String name;
//对UserInfo进一步校验
//这里只能使用@Valid,不能用 @Validated。但valid不支持分组校验,想要支持分组校验可以使用自定义参数校验。
@NotEmpty
private List<@NotNull @Valid UserInfo> parents;
}
7,自定义参数校验
虽然JSR303和Hibernate Validtor 已经提供了很多校验注解,但是当面对复杂参数校验时,还是不能满足我们的要求,这时候我们就需要自定义校验注解。
举例:自定义一个List数组中不能含有null元素的注解。
- 自定义注解
message、groups、payload属性都需要定义在参数校验注解中不能缺省。
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//此处指定了注解的实现类为ListNotHasNullValidatorImpl
@Constraint(validatedBy = ListNotHasNullValidatorImpl.class)
public @interface ListNotHasNull {
/**
* 添加value属性,可以作为校验时的条件,若不需要,可去掉此处定义
*/
int value() default 0;
String message() default "List集合中不能含有null元素";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* 定义List,为了让Bean的一个属性上可以添加多套规则
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@interface List {
ListNotHasNull[] value();
}
}
- 注解实现类 该类需要实现ConstraintValidator
import org.springframework.stereotype.Service;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.List;
public class ListNotHasNullValidatorImpl implements ConstraintValidator<ListNotHasNull, List> {
private int value;
@Override
public void initialize(ListNotHasNull constraintAnnotation) {
//传入value 值,可以在校验中使用
this.value = constraintAnnotation.value();
}
public boolean isValid(List list, ConstraintValidatorContext constraintValidatorContext) {
for (Object object : list) {
if (object == null) {
//如果List集合中含有Null元素,校验失败
return false;
}
}
return true;
}
}
- 正常使用
@NotEmpty
@ListNotHasNull
private List<@Valid UserInfo> parents;
8,统一异常捕获
如果有很多使用这种参数验证的controller方法,我们希望在一个地方对ConstraintViolationException异常进行统一处理,可以使用统一异常捕获,这需要借助@ControllerAdvice注解来实现,当然在springboot中我们就用@RestControllerAdvice(内部包含@ControllerAdvice和@ResponseBody的特性)
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.Set;
/**
* @author pengchengbai
* @date 2019-06-01 14:09
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handle(ValidationException exception) {
if(exception instanceof ConstraintViolationException){
ConstraintViolationException exs = (ConstraintViolationException) exception;
Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
for (ConstraintViolation<?> item : violations) {
//打印验证不通过的信息
System.out.println(item.getMessage());
}
}
return "bad request" ;
}
}
当参数校验异常的时候,该统一异常处理类在控制台打印信息的同时把bad request的字符串和HttpStatus.BAD_REQUEST所表示的状态码400返回给调用方(用@ResponseBody注解实现,表示该方法的返回结果直接写入HTTP response body 中)。其中:
@ControllerAdvice:控制器增强,使@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法应用到所有的 @RequestMapping注解的方法。
@ExceptionHandler:异常处理器,此注解的作用是当出现其定义的异常时进行处理的方法,此例中处理ValidationException异常。
9,校验模式
- 普通模式(默认是这个模式): 会校验完所有的属性,然后返回所有的验证失败信息
- 快速失败模式: 只要有一个验证失败,则返回
如果想要配置第二种模式,需要添加如下配置类:
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
@Configuration
public class ValidatorConf {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}
10,自定义校验注解
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = {CheckValueInList.class})
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface ValueInList {
String message() default "(查询字段不支持该值)Query value not be supported";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] values() default {};
}
controller接口参数添加注解,表示值必须是列表中的内容。
@ValueInList(values = {"name", "-id","age" })


被折叠的 条评论
为什么被折叠?



