你还在手动对数据进行校验,快来使用validation吧

       本篇主要讲解使用javax.validation.constraints,org.hibernate.validator.constraints下的校验方法对实体类进行自动校验, 直接对数据进行校验,通过对接收的数据进行校验,如果不符合我们定义的要求则会提示对应的message信息,具体怎么做的,下面一步步来说明。

初步介绍校验

先看下javax.validation.constraints下面的一些数据校验方法:

AssertFalse 
AssertTrue
DecimalMax
DecimalMin
Digits
Email
Future
FutureOrPresent
Min
Max
Negative
NegativeOrZero
NotBlank
NotEmpty
NotNull
Null
Past
PastOrPresent
Pattern
Positive
PositiveOrZero
Size

再来看下org.hibernate.validator.constraints部分校验方法

Length
URL
Range
SafeHtml

具体怎么使用不是本节的重点,大家可以在需要用到的时候参考源码获取网上查阅资料,下面据两个例子来讲解:

@Length(min = 1, max = 30, message = "规则描述字段长度需要在{min}和{max}之间", groups = {InsertGroup.class, UpdateGroup.class})
private String ruleDesc;
@ApiModelProperty("规则优先级")
@NotEmpty(message="规则优先级不可为空",groups = {InsertGroup.class,UpdateGroup.class})
@Pattern(regexp = RegexpUtils.NON_ZERO_NEGATIVE_INTEGERS_REGEXP,message = "规则优先级必须为正整数")
private String salience;

使用上面的校验需要引入依赖:

spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

如果是Spring Mvc,那可以直接添加hibernate-validator依赖

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>7.0.1.Final</version>
</dependency> 

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
</dependency>

 javax.validation在spring-boot-starter-web包也已经被引入了

校验分组

如果需要分组,可以定义分组:

public interface UpdateGroup extends Update {
}
import org.apache.ibatis.annotations.Insert;

public interface InsertGroup extends Insert {
}

hibernate的校验模式

Hibernate Validator有以下两种验证模式:

1 、普通模式(默认是这个模式)

  普通模式(会校验完所有的属性,然后返回所有的验证失败信息)

2、快速失败返回模式

  快速失败返回模式(只要有一个验证失败,则返回)

默认是普通模式

配置校验模式:

@Configuration
public class ValidatorConfiguration {
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .addProperty("hibernate.validator.fail_fast", "true")
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();

        return validator;
    }
}

rest接口参数校验

对@PathVariable和@RequestParam参数进行校验,需要在入参声明约束的注释

@GetMapping
    public Result getByUri(@RequestParam @NotEmpty String uri) {
        List<GatewayRoutePO> list = gatewayRoutService.query(GatewayRouteQueryParam.builder().uri(uri).build());
        return Result.success(Optional.ofNullable(list).get().stream().findFirst());
    }
  @GetMapping(value = "/get/{id}")
    public Result get(@PathVariable(name = "id") @NotBlank String routId) {
        LOGGER.info("get with id:{}", routId);
        return Result.success(gatewayRoutService.getRouteByRouteId(routId));
    }

对于RequestBody的参数校验直接在实体参数前加上 Validated 或者javax.validation.Valid

  @PostMapping(value = "/conditions")
    public Result search(@Validated @RequestBody GatewayRouteQueryParam gatewayRouteQueryParam) {
        return Result.success(gatewayRoutService.query(gatewayRouteQueryParam));
    }

校验原理

 这里需要提到RequestResponseBodyMethodProcessor,这个类有两个作用,

1:用于解析@RequestBody标注的参数

2:处理ResponseBody标注方法的返回值

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}

	@Override
	public boolean supportsReturnType(MethodParameter returnType) {
		return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
				returnType.hasMethodAnnotation(ResponseBody.class));
	}

解析RequestBody标注参数的方法是resolveArgument

@Override
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		parameter = parameter.nestedIfOptional();
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
			if (arg != null) {
                //进行参数校验
				validateIfApplicable(binder, parameter);
                //校验不通过抛MethodArgumentNotValidException异常
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
				}
			}
			if (mavContainer != null) {
				mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
			}
		}

		return adaptArgumentIfNecessary(arg, parameter);
	}

 校验方法:AbstractMessageConverterMethodArgumentResolver#validateIfApplicable 获取Validated注解,根据注解参数和注解信息验证DataBinder#validate

	protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
		Annotation[] annotations = parameter.getParameterAnnotations();
		for (Annotation ann : annotations) {
			Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
			if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
				Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
				Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
				binder.validate(validationHints);
				break;
			}
		}
	}
	public void validate(Object... validationHints) {
		Object target = getTarget();
		Assert.state(target != null, "No target to validate");
		BindingResult bindingResult = getBindingResult();
		// Call each validator with the same binding result
		for (Validator validator : getValidators()) {
			if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
				((SmartValidator) validator).validate(target, bindingResult, validationHints);
			}
			else if (validator != null) {
				validator.validate(target, bindingResult);
			}
		}
	}

通过在参数上加注释校验参数是否符合条件, 实际上就是使用AOP拦截,Spring通过MethodValidationPostProcessor动态主从AOP切面,然后使用MethodValidationInterceptor对切点进行织入增强

public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
		implements InitializingBean {

	@Override
	public void afterPropertiesSet() {
       //创建切面
		Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
       //创建advisor进行增强
		this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
	}


}

自定义校验:

定义自定义约束,有三个步骤

  • 创建约束注解
  • 实现一个验证器
  • 定义默认的错误信息
自定义约束注解:
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Constraint(validatedBy = {MobileValidator.class})
@Retention(RUNTIME)
@Repeatable(Mobile.List.class)
public @interface Mobile {

    /**
     * 错误提示信息,可以写死,也可以填写国际化的key
     */
    String message() default "手机号码不正确";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String regexp() default "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";

    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        Mobile[] value();
    }
}
public class MobileValidator implements ConstraintValidator<Mobile, String> {

    /**
     * 手机验证规则
     */
    private Pattern pattern;

    @Override
    public void initialize(Mobile mobile) {
        pattern = Pattern.compile(mobile.regexp());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }

        return pattern.matcher(value).matches();
    }
}

测试自定义校验:

import org.springframework.util.CollectionUtils;
 
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Iterator;
import java.util.Set;
 
public class ValidatorUtil {
 
    private static final Validator validator= Validation.buildDefaultValidatorFactory().getValidator();
 
    public static <T> String validate(T object){
        Set<ConstraintViolation<T>> violationSet=validator.validate(object);
        if(violationSet!=null&&!CollectionUtils.isEmpty(violationSet)){
            Iterator<ConstraintViolation<T>> iterator=violationSet.iterator();
            StringBuffer sb=new StringBuffer(64);
            while(iterator.hasNext()){
                sb.append(iterator.next().getMessage()).append(";");
            }
            return sb.toString();
        }
        return null;
    }
}

配置全局校验

/**
 * 全局参数验证
 */
@Slf4j
@Order(1)
@ControllerAdvice
@RestController
public class ValidationException {

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public CommonResult ValidExceptionHandler(MethodArgumentNotValidException exception) throws Exception {
        BindingResult bindingResult = exception.getBindingResult();
        if (bindingResult.hasErrors()) {
            String defaultMessage = bindingResult.getFieldError().getDefaultMessage();
            return CommonResult.failed(defaultMessage);
        }
        return CommonResult.success("校验通过");
    }
}

在接口中使用数据校验:

@PostMapping("/save")
@ApiOperation(value = "保存规则数据")
public CommonResult saveCvbRule(@Validated(InsertGroup.class) @RequestBody Rule rule) 

如果校验不通过会向前端返回message校验失败信息

参考资料:基于springboot使用hibernate validator校验数据

hibernate-validation官网

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员路同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值