SpringCloud里参数校验/参数验证

1,076 阅读5分钟

1、前言

在控制器类的方法里自己写校验逻辑代码当然也可以,只是代码比较丑陋,有点“low”。业界有更好的处理方法,分别阐述如下。

2、PathVariable校验

@GetMapping("/path/{group:[a-zA-Z0-9_]+}/{userid}")

@ResponseBody

public String path(@PathVariable("group") String group, @PathVariable("userid") Integer userid) {

returngroup +":"+ userid;

}

用法是:路径变量:正则表达式。当请求URI不满足正则表达式时,客户端将收到404错误码。不方便的地方是,不能通过捕获异常的方式,向前端返回统一的、自定义格式的响应参数。

3、方法参数校验

@GetMapping("/validate1")

@ResponseBody

public String validate1(

@Size(min = 1,max = 10,message ="姓名长度必须为1到10")@RequestParam("name") String name,

@Min(value = 10,message ="年龄最小为10")@Max(value = 100,message ="年龄最大为100") @RequestParam("age") Integer age) {

return"validate1";

}

如果前端传递的参数不满足规则,则抛出异常。注解Size、Min、Max来自validation-api.jar,更多注解参见相关标准小节。

4、表单对象/VO对象校验

当参数是VO时,可以在VO类的属性上添加校验注解。

public class User {

@Size(min = 1,max = 10,message ="姓名长度必须为1到10")

private String name;

@NotEmpty

private String firstName;

@Min(value = 10,message ="年龄最小为10")@Max(value = 100,message ="年龄最大为100")

private Integer age;

@Future

@JSONField(format="yyyy-MM-dd HH:mm:ss")

private Date birth;

。。。

}

其中,Future注解要求必须是相对当前时间来讲“未来的”某个时间。

@PostMapping("/validate2")

@ResponseBody

public User validate2(@Valid @RequestBody User user){

returnuser;

}

5、自定义校验规则

5.1 自定义注解校验

需要自定义一个注解类和一个校验类。

import javax.validation.Constraint;

import javax.validation.Payload;

import java.lang.annotation.*;

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.PARAMETER,ElementType.FIELD})

@Constraint(validatedBy = FlagValidatorClass.class)

public @interface FlagValidator {

// flag的有效值,多个使用,隔开

String values();

// flag无效时的提示内容

String message() default"flag必须是预定义的那几个值,不能随便写";

Class[] groups() default {};

Class[] payload() default {};

}

import javax.validation.ConstraintValidator;

import javax.validation.ConstraintValidatorContext;

public class FlagValidatorClass implements ConstraintValidator {

/**

  • FlagValidator注解规定的那些有效值

*/

private String values;

@Override

public void initialize(FlagValidator flagValidator) {

this.values = flagValidator.values();

}

/**

  • 用户输入的值,必须是FlagValidator注解规定的那些值其中之一。

  • 否则,校验不通过。

  • @param value 用户输入的值,如从前端传入的某个值

*/

@Override

public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {

// 切割获取值

String[] value_array = values.split(",");

Boolean isFlag =false;

for(int i = 0; i < value_array.length; i++){

// 存在一致就跳出循环

if(value_array[i] .equals(value)){

isFlag =true;break;

}

}

returnisFlag;

}

}

使用我们自定义的注解:

public class User {

// 前端传入的flag值必须是1或2或3,否则校验失败

@FlagValidator(values ="1,2,3")

private String flag ;

。。。

}

5.2 分组校验

import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.Min;

import javax.validation.constraints.NotNull;

public class Resume {

public interface Default {

}

public interface Update {

}

@NotNull(message ="id不能为空", groups = Update.class)

private Long id;

@NotNull(message ="名字不能为空", groups = Default.class)

@Length(min = 4, max = 10, message ="name 长度必须在 {min} - {max} 之间", groups = Default.class)

private String name;

@NotNull(message ="年龄不能为空", groups = Default.class)

@Min(value = 18, message ="年龄不能小于18岁", groups = Default.class)

private Integer age;

。。。

}

/**

  • 使用Defaul分组进行验证

  • @param resume

  • @return

*/

@PostMapping("/validate5")

public String addUser(@Validated(value = Resume.Default.class) @RequestBody Resume resume) {

return"validate5";

}

/**

  • 使用Default、Update分组进行验证

  • @param resume

  • @return

*/

@PutMapping("/validate6")

public String updateUser(@Validated(value = {Resume.Update.class, Resume.Default.class}) @RequestBody Resume resume) {

return"validate6";

}

建立了两个分组,名称分别为Default、Update。POST方法提交时使用Defaut分组的校验规则,PUT方法提交时同时使用两个分组规则。

6、异常拦截器

通过设置全局异常处理器,统一向前端返回校验失败信息。

import com.scj.springbootdemo.WebResult;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.util.CollectionUtils;

import org.springframework.validation.ObjectError;

import org.springframework.web.bind.MethodArgumentNotValidException;

import org.springframework.web.bind.annotation.ControllerAdvice;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.bind.annotation.ResponseBody;

import javax.validation.ConstraintViolation;

import javax.validation.ConstraintViolationException;

import java.util.List;

import java.util.Set;

/**

  • 全局异常处理器

*/

@ControllerAdvice

public class GlobalExceptionHandler {

private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

/**

  • 用来处理bean validation异常

  • @param ex

  • @return

*/

@ExceptionHandler(ConstraintViolationException.class)

@ResponseBody

public WebResult resolveConstraintViolationException(ConstraintViolationException ex){

WebResult errorWebResult = new WebResult(WebResult.FAILED);

Set> constraintViolations = ex.getConstraintViolations();

if(!CollectionUtils.isEmpty(constraintViolations)){

StringBuilder msgBuilder = new StringBuilder();

for(ConstraintViolation constraintViolation :constraintViolations){

msgBuilder.append(constraintViolation.getMessage()).append(",");

}

String errorMessage = msgBuilder.toString();

if(errorMessage.length()>1){

errorMessage = errorMessage.substring(0,errorMessage.length()-1);

}

errorWebResult.setInfo(errorMessage);

returnerrorWebResult;

}

errorWebResult.setInfo(ex.getMessage());

returnerrorWebResult;

}

@ExceptionHandler(MethodArgumentNotValidException.class)

@ResponseBody

public WebResult resolveMethodArgumentNotValidException(MethodArgumentNotValidException ex){

WebResult errorWebResult = new WebResult(WebResult.FAILED);

List objectErrors = ex.getBindingResult().getAllErrors();

if(!CollectionUtils.isEmpty(objectErrors)) {

StringBuilder msgBuilder = new StringBuilder();

for(ObjectError objectError : objectErrors) {

msgBuilder.append(objectError.getDefaultMessage()).append(",");

}

String errorMessage = msgBuilder.toString();

if(errorMessage.length() > 1) {

errorMessage = errorMessage.substring(0, errorMessage.length() - 1);

}

errorWebResult.setInfo(errorMessage);

returnerrorWebResult;

}

errorWebResult.setInfo(ex.getMessage());

returnerrorWebResult;

}

}

我这里准备了一些 【Java核心技术资料】 

【JAVA核心总结】 【524页中高级程序员必备知识】

 【《JavaGuide面试突击》v3.0肝出来了!】 

【Java高级笔试宝典覆盖近3年Java笔试中98%高频知识点吊打100家大厂面试官】

 【Java大厂面试题】 

【阿里架构师花近十年时间整理出来的Java核心知识pdf(Java岗)】

 【524页《Java中高级程序员必备核心知识》总结,令人犹如醍醐灌顶】

 等一系列的Java架构资料 需要的话扫一扫免费获取 无套路

7、相关标准

JSR 303 是Bean验证的规范 ,Hibernate Validator 是该规范的参考实现,它除了实现规范要求的注解外,还额外实现了一些注解。

validation-api-1.1.0.jar 包括如下约束注解:

约束注解说明

@AssertFalse被注释的元素必须为 false

@AssertTrue被注释的元素必须为 true

@DecimalMax(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内

@Null被注释的元素必须为 null

@NotNull被注释的元素必须不为 null

@Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@Size(max, min)被注释的元素的大小必须在指定的范围内

@Past被注释的元素必须是一个过去的日期

@Future被注释的元素必须是一个将来的日期

@Pattern(value)被注释的元素必须符合指定的正则表达式

hibernate-validator-5.3.6.jar 包括如下约束注解:

约束注解说明

@Email被注释的元素必须是电子邮箱地址

@Length被注释的字符串的大小必须在指定的范围内

@NotBlank被注释的字符串的必须非空

@NotEmpty被注释的字符串、集合、Map、数组必须非空

@Range被注释的元素必须在合适的范围内

@SafeHtml被注释的元素必须是安全Html

@URL被注释的元素必须是有效URL

8、参数校验原理

这篇文章 写得比较深入,我没有太理解。

9、本文源码

公司不让上传源码到GitHub,可以参加这篇文章。

10、同时校验2个或更多个字段/参数

常见的场景之一是,查询某信息时要输入开始时间和结束时间。显然,结束时间要≥开始时间。可以在查询VO类上使用自定义注解,下面的例子来自这里。划重点:@ValidAddress使用在类上。

@ValidAddress

public class Address {

@NotNull

@Size(max = 50)

private String street1;

@Size(max = 50)

private String street2;

@NotNull

@Size(max = 10)

private String zipCode;

@NotNull

@Size(max = 20)

private String city;

@Valid

@NotNull

private Country country;

// Getters and setters

}

public class Country {

@NotNull

@Size(min = 2, max = 2)

private String iso2;

// Getters and setters

}

@Documented

@Target(TYPE)

@Retention(RUNTIME)

@Constraint(validatedBy = { MultiCountryAddressValidator.class })

public @interface ValidAddress {

String message() default"{com.example.validation.ValidAddress.message}";

Class[] groups() default {};

Class[] payload() default {};

}

public class MultiCountryAddressValidator

implements ConstraintValidator {

public void initialize(ValidAddress constraintAnnotation) {

}

@Override

public boolean isValid(Address address,

ConstraintValidatorContext constraintValidatorContext) {

Country country = address.getCountry();

if(country == null || country.getIso2() == null || address.getZipCode() == null) {

returntrue;

}

switch (country.getIso2()) {

case"FR":

return// Checkifaddress.getZipCode() is validforFrance

case"GR":

return// Checkifaddress.getZipCode() is validforGreece

default:

returntrue;

}

}

}