在web开发中,前端的参数校验是为了用户体验,后端的参数校验是为了安全。
参数有两种形式:
一般用于GET请求,会以url string的形式进行传递
一般用于POST请求,可以使用Content-Type来指定不同参数类型
spring-boot可能目前并不支持对参数的验证。
注意:单个参数校验在类上添加注解:@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格式化,将字符串转换为数字 |
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@NumberFormat(pattern="#,###")
private Integer salary;
@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;
}
}
当处理post请求或者请求参数较多的时候使用一个bean来接收参数,然后在每个需要校验的属性上使用参数校验注解。
@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) {
}
用同一个实体类去接收多个controller的参数,但是不同controller所需要的参数又有些许不同。比如有一个/setUser接口不需要id参数,而/getUser接口又需要该参数,这种时候就可以使用参数分组来实现。
public interface GroupA {
}
@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;
}
当参数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;
}
虽然JSR303和Hibernate Validtor 已经提供了很多校验注解,但是当面对复杂参数校验时,还是不能满足我们的要求,这时候我们就需要自定义校验注解。
举例:自定义一个List数组中不能含有null元素的注解。
@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();
}
}
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;
如果有很多使用这种参数验证的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异常。
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;
}
}
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" })