【java学习】参数校验

1,概念

在web开发中,前端的参数校验是为了用户体验,后端的参数校验是为了安全。
参数有两种形式:

1)查询字符串参数(Query String Parameters参数)

一般用于GET请求,会以url string的形式进行传递

2)请求体参数(Request Body)

一般用于POST请求,可以使用Content-Type来指定不同参数类型

3)路径参数校验pathvariable

spring-boot可能目前并不支持对参数的验证。

2,校验注解

注意:单个参数校验在类上添加注解:@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 所注解的元素需满足Email格式
@DateTimeFormat 日期格式化注解,将字符串转换为日期格式进行接收
@NumberFormat number格式化,将字符串转换为数字

1)@DateTimeFormat

@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
  1. 请求非JSON数据,建议用@DateTimeFormat即可,此时不会格式化返回数据(比如get请求,当然get请求也可以请求JSON数据,只是不推荐);
  2. 请求JSON数据,建议用@ReqeustBody来转换数据,然后搭配局部注解@JsonFormat(会格式化返回数据)或者全局配置来修改默认的日期解析格式(默认"yyyy-MM-dd’T’HH:mm:ss.SSSX");全局配置也可以格式化返回数据,需配置builder.serializerByType
  3. 如果日期格式化出错,先看传来的数据是否为JSON数据(可以通过consumes来限制),然后再看有没有对于的注解或日期格式化全局配置

2)@NumberFormat

  @NumberFormat(pattern="#,###")
    private Integer salary;

3,单个参数校验

  1. 在类上添加注解:@Validated
  2. 在接口使用注解进行校验。
@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来接收参数,然后在每个需要校验的属性上使用参数校验注解。

  1. 然后在controller类上添加注解:@Validated;
  2. 在方法中对@RequestBody的参数用@Valid注解;
  3. 在具体的实体类使用对应的注解进行校验即可。
@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接口又需要该参数,这种时候就可以使用参数分组来实现。

  1. 定义表示组别的interface;
public interface GroupA {
}
  1. 在@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();
    }
  1. 在实体类的注解中标记这个哪个组所使用的参数;
@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元素的注解。

  1. 自定义注解
    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();
    }
}
  1. 注解实现类 该类需要实现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;
    }
}

  1. 正常使用
@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,校验模式

  1. 普通模式(默认是这个模式): 会校验完所有的属性,然后返回所有的验证失败信息
  2. 快速失败模式: 只要有一个验证失败,则返回
    如果想要配置第二种模式,需要添加如下配置类:
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" })

你可能感兴趣的:(java学习,java,学习,开发语言)