Java validator整理

Java validator整理

因为想对方法的入参和出参作简单的非空或者非空字符做校验,所以找了下相关的@NotNull注解

说明
javax.validation.constraints.NotNull Java提供的JSR校验规范
org.jetbrains.annotations.NotNull idea提供的校验注解,只在使用idea工具时有效,主要起注释功能,提醒调用者
com.sun.istack.internal 还不清楚

这里主要看下Java的JSR规范提供的校验
主要的实现框架有Hibernate validation和Spring Validation
依赖jar

<dependency>
   <groupId>javax.el</groupId>
   <artifactId>javax.el-api</artifactId>
   <version>2.2.4</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.1.3.Final</version>
</dependency>

JSR-303原生支持的限制有如下几种:

限制 说明
@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Past 限制必须是一个过去的日期
@Pattern(value) 定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间

除此之外,hibernate也还提供了其它的限制校验,在org.hibernate.validator.constraints包下
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
单独使用hibernate validation校验bean对象
参考ytasd项目代码

public class ValidatorUtil {
    private static Validator validator = Validation.buildDefaultValidatorFactory()
            .getValidator();
    public static <T> ValidatorResult validate(T obj){
        ValidatorResult result = new ValidatorResult();
        Set<ConstraintViolation<T>> set = validator.validate(obj,Default.class);
        if( CollectionUtils.isNotEmpty(set) ){
            result.setHasErrors(true);
            Map<String,String> errorMsg = new HashMap<String,String>();
            for(ConstraintViolation<T> cv : set){
                errorMsg.put(cv.getPropertyPath().toString(), cv.getMessage());
            }
            result.setErrorMsg(errorMsg);
        }
        return result;
    }

    public static <T> ValidatorResult validateProperty(T obj,String propertyName){
        ValidatorResult result = new ValidatorResult();
        Set<ConstraintViolation<T>> set = validator.validateProperty(obj,propertyName,Default.class);
        if( CollectionUtils.isNotEmpty(set) ){
            result.setHasErrors(true);
            Map<String,String> errorMsg = new HashMap<String,String>();
            for(ConstraintViolation<T> cv : set){
                errorMsg.put(propertyName, cv.getMessage());
            }
            result.setErrorMsg(errorMsg);
        }
        return result;
    }
}

整合Spring
Bean校验处理器:BeanValidationPostProcessor
方法校验处理器:MethodValidationPostProcessor
这里我们主要关心对方法的入参、出参校验,所以主要看下MethodValidationPostProcessor的实现
关键代码

private Class<? extends Annotation> validatedAnnotationType = Validated.class;
@Override
	public void afterPropertiesSet() {
		Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
		Advice advice = (this.validator != null ? new MethodValidationInterceptor(this.validator) :
				new MethodValidationInterceptor());
		this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
	}

通过这段代码,我们可以看出,它主要对使用了Validated注解的类作拦截,拦截器是MethodValidationInterceptor
核心代码

private static Method forExecutablesMethod;

	private static Method validateParametersMethod;

	private static Method validateReturnValueMethod;

	static {
		try {
		//这里拿到ExecutableValidator
			forExecutablesMethod = Validator.class.getMethod("forExecutables");
			Class<?> executableValidatorClass = forExecutablesMethod.getReturnType();
			validateParametersMethod = executableValidatorClass.getMethod(
					"validateParameters", Object.class, Method.class, Object[].class, Class[].class);
			validateReturnValueMethod = executableValidatorClass.getMethod(
					"validateReturnValue", Object.class, Method.class, Object.class, Class[].class);
		}
		catch (Exception ex) {
			// Bean Validation 1.1 ExecutableValidator API not available
		}
	}
public Object invoke(MethodInvocation invocation) throws Throwable {
		Class<?>[] groups = determineValidationGroups(invocation);
		if (forExecutablesMethod != null) {
			Object executableValidator = ReflectionUtils.invokeMethod(forExecutablesMethod, this.validator);
			Set<ConstraintViolation<?>> result = (Set<ConstraintViolation<?>>)
					ReflectionUtils.invokeMethod(validateParametersMethod, executableValidator,
							invocation.getThis(), invocation.getMethod(), invocation.getArguments(), groups);
			if (!result.isEmpty()) {
				throw new ConstraintViolationException(result);
			}
			Object returnValue = invocation.proceed();
			result = (Set<ConstraintViolation<?>>)
					ReflectionUtils.invokeMethod(validateReturnValueMethod, executableValidator,
							invocation.getThis(), invocation.getMethod(), returnValue, groups);
			if (!result.isEmpty()) {
				throw new ConstraintViolationException(result);
			}
			return returnValue;
		}
		else {
			return HibernateValidatorDelegate.invokeWithinValidation(invocation, this.validator, groups);
		}
	}

ExecutableValidator是关键的接口,它提供了对方法入参和返回值的校验

validateParameters(T object, Method method, Object[] parameterValues, Class... groups);

validateReturnValue(T object, Method method, Object returnValue, Class... groups);

validateConstructorParameters(Constructor constructor, Object[] parameterValues, Class... groups);

validateConstructorReturnValue(Constructor constructor, T createdObject, Class... groups);

返回的都是Set>,ConstraintViolation中封装了具体的信息

通过MethodValidationInterceptor可以看出,它主要帮我们做了自动调用校验方法的逻辑,但使用上比较麻烦,需要在类上加上Validated

默认只对@org.springframework.validation.annotation.Validated注解的Bean进行验证,我们可以修改validatedAnnotationType为其他注解类型来支持其他注解验证。而且目前只支持Hibernate Validator实现,在未来版本可能支持其他实现。
Controller层的校验只能使用BeanValidator?

我们可以自己写个aop调用MethodValidationInterceptor

<bean class="org.springframework.validation.beanvalidation.MethodValidationInterceptor" id="validationInterceptor" />
    <aop:config>
        <aop:pointcut id="validator" expression="execution(* com.yt.trade..*(..))" />
        <aop:advisor advice-ref="validationInterceptor" pointcut-ref="validator"/>
    </aop:config>

限制:

  1. 只能对Spring代理的类和方法进行验证,不能在内部调用中起效。
  2. 返回的信息类中没有参数名,提示不直观
    基于这两个问题,我们可以自己写拦截器作校验

自定义校验器

实现ConstraintValidator接口

@Target({ElementType.FIELD, ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
@Constraint(validatedBy=MinValidator.class)  
public @interface Min {  
   
    int value() default 0;  
     
    String message();  
     
    Class<?>[] groups() default {};  
     
    Class<? extends Payload>[] payload() default {};  
}  
public class MinValidator implements ConstraintValidator<Min, Integer> {  
   
    private int minValue;  
     
    public void initialize(Min min) {  
       // TODO Auto-generated method stub  
       //把Min限制类型的属性value赋值给当前ConstraintValidator的成员变量minValue  
       minValue = min.value();  
    }  
   
    public boolean isValid(Integer value, ConstraintValidatorContext arg1) {  
       // TODO Auto-generated method stub  
       //在这里我们就可以通过当前ConstraintValidator的成员变量minValue访问到当前限制类型Min的value属性了  
       return value >= minValue;  
    }  
   
}  

参考:

Spring3.1 对Bean Validation规范的新支持(方法级别验证)
http://www.iteye.com/topic/1122937
hibernate Validation使用示例及讲解
在系统中使用Bean Validation验证参数
http://haohaoxuexi.iteye.com/blog/1812584
http://my.oschina.net/qjx1208/blog/200946

原文地址:https://www.cnblogs.com/yissheng/p/5627820.html