zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    mysql启动失败一例
    Windows 10 执行pip list报错 UnicodeDecodeError: 'gbk' codec can't decode
    GPT转MBR
    python_查找模块的方法
    python_函数中使用*和**
    Python_单元测试工具nose
    状态迁移法
    场景分析法
    可爱的Python_课后习题_CDay0 时刻准备着!发布
    可爱的Python_课后习题_CDay−2 完成核心功能
  • 原文地址:https://www.cnblogs.com/yissheng/p/5627820.html
Copyright © 2011-2022 走看看