• Hibernate-validator校验含javax.validation.constraints注解的对象其首次校验长耗时问题


    前段时间对老项目做性能优化时,发现用hibernate-validator校验数据约束,首次检验某个实体类耗时较长,本文探讨其中的原因,并给出优化建议。

    1. 校验测试

    ValidateTest1DTO.java代码如下

    package com.mingo.exp.validate;
    
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.NotNull;
    
    /**
     * 被校验类
     *
     * @author doflamingo
     */
    @Data
    @NoArgsConstructor
    public class ValidateTest1DTO {
    
        @NotBlank(message = "name不能为空")
        private String name;
    
        @NotNull(message = "score不能为空")
        private Double score;
    
        public ValidateTest1DTO(String name, Double score) {
            this.name = name;
            this.score = score;
        }
    }
    

    HibernateValidateTest.java测试类如下

    package com.mingo.exp.validate;
    
    import org.springframework.util.StopWatch;
    
    import javax.validation.Validation;
    import javax.validation.Validator;
    
    /**
     * 运行方式:
     * 1、执行main
     *
     * @author doflamingo
     */
    public class HibernateValidateTest {
    
        // 1 ms = 1000000 ns
    
        public void test() {
    
            // org.hibernate.validator.internal.engine.ValidatorFactoryImpl
            Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
    
            ValidateTest1DTO t1 = new ValidateTest1DTO("A", 3.1415);
            ValidateTest1DTO t2 = new ValidateTest1DTO("B", 3.1415);
            ValidateTest1DTO t3 = new ValidateTest1DTO("C", null);
            ValidateTest1DTO t4 = new ValidateTest1DTO("", null);
    
            StopWatch stopWatch = new StopWatch("时间测试");
    
            // t1
            stopWatch.start("t1");
            validator.validate(t1);
            stopWatch.stop();
    
            // t2
            stopWatch.start("t2");
            validator.validate(t2);
            stopWatch.stop();
    
            // t3
            stopWatch.start("t3");
            validator.validate(t3);
            stopWatch.stop();
    
            // t4
            stopWatch.start("t4");
            validator.validate(t4);
            stopWatch.stop();
    
            System.out.println(stopWatch.prettyPrint());
        }
    
        public static void main(String[] args) {
            new HibernateValidateTest().test();
        }
    
    }
    

    运行多次 HibernateValidateTest.main() 结果都一致,取其中一个结果

    StopWatch '时间测试': running time = 93152700 ns
    ---------------------------------------------
    ns         %     Task name
    ---------------------------------------------
    089612800  096%  t1
    000115200  000%  t2
    003283800  004%  t3
    000140900  000%  t4
    

    t1、t2、t3和t4对象类型和校验都是一样,但运行结果显示"t1"(首次校验)耗时较长,下面看下具体原因

    2. 校验耗时原因分析

    检验方法org.hibernate.validator.internal.engine.ValidatorImpl.validate(T object, Class<?>... groups)源码如下

    分析了该方法所有代码,在红色框处代码调用有些不同,进入org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.getBeanMetaData(Class beanClass)方法查看

    红色方框处有一个逻辑是根据被校验类的Class对象从一个Map中获取BeanMetaData对象,如果Map中没有就创建BeanMetaData对象并存入Map中,下次同样的Class对象传进来直接从Map获取到即可。t1与t2~t4的Class对象一样,最大的不同是校验t1时调用了createBeanMetaData( normalizedBeanClass )方法,用于生成BeanMetaData对象,该方法是个耗时操作。

    下面对上述耗时原因做了时间验证

    3. validate(T object, Class<?>... groups)方法内部代码时间测试

    源代码在启动时会生成org.hibernate.validator.internal.engine.ValidatorFactoryImpl对象,采用工厂方法getValidator()生成Validator校验对象,源码如下

    @Override
    public Validator getValidator() {
        return createValidator(
                constraintCreationContext.getConstraintValidatorManager().getDefaultConstraintValidatorFactory(),
                constraintCreationContext,
                validatorFactoryScopedContext,
                methodValidationConfiguration
        );
    }
    

    所以我只需代理该方法生成自己的Validator对象。

    总体思路

    1. 复制org.hibernate.validator.internal.engine.ValidatorImpl代码为类com.mingo.exp.validate.MyCopyValidatorImpl,只是构造器不一样;
    2. 修改MyCopyValidatorImpl.validate(T object, Class<?>... groups)方法,加入时间测试代码;
    3. 用JDK动态代理ValidatorFactoryImpl类,处理getValidator()方法,生成MyCopyValidatorImpl对象;

    MyCopyValidatorImpl.validate(T object, Class<?>... groups)方法

    @Override
    public final <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
    
        // 将源代码分成了五部分测试
    
        StopWatch stopWatch = new StopWatch("hibernate.validator.validate(T object, Class<?>... groups)时间测试");
    
        stopWatch.start("第一段");
        Contracts.assertNotNull(object, MESSAGES.validatedObjectMustNotBeNull());
        sanityCheckGroups(groups);
        stopWatch.stop();
    
        stopWatch.start("第二段");
        @SuppressWarnings("unchecked")
        Class<T> rootBeanClass = (Class<T>) object.getClass();
        BeanMetaData<T> rootBeanMetaData = beanMetaDataManager.getBeanMetaData(rootBeanClass);
        stopWatch.stop();
    
        stopWatch.start("第三段");
        if (!rootBeanMetaData.hasConstraints()) {
            return Collections.emptySet();
        }
        BaseBeanValidationContext<T> validationContext = getValidationContextBuilder().forValidate(rootBeanClass, rootBeanMetaData, object);
        stopWatch.stop();
    
        stopWatch.start("第四段");
        ValidationOrder validationOrder = determineGroupValidationOrder(groups);
        stopWatch.stop();
    
        stopWatch.start("第五段");
        BeanValueContext<?, Object> valueContext = ValueContexts.getLocalExecutionContextForBean(
                validatorScopedContext.getParameterNameProvider(),
                object,
                validationContext.getRootBeanMetaData(),
                PathImpl.createRootPath()
        );
        stopWatch.stop();
    
        // 打印测试结果
        System.out.println(stopWatch.prettyPrint());
    
        return validateInContext(validationContext, valueContext, validationOrder);
    }
    

    MyCopyValidatorImpl类其余方法与ValidatorImpl一样,这里不给出

    com.mingo.exp.validate.ValidatorFactoryImplProxy代理类

    package com.mingo.exp.validate;
    
    import org.hibernate.validator.internal.engine.ConstraintCreationContext;
    import org.hibernate.validator.internal.engine.MethodValidationConfiguration;
    import org.hibernate.validator.internal.engine.ValidatorFactoryScopedContext;
    import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator;
    import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager;
    import org.hibernate.validator.internal.metadata.BeanMetaDataManager;
    import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl;
    import org.hibernate.validator.internal.metadata.provider.MetaDataProvider;
    import org.hibernate.validator.internal.properties.javabean.JavaBeanHelper;
    import org.hibernate.validator.internal.util.ExecutableHelper;
    import org.hibernate.validator.internal.util.ExecutableParameterNameProvider;
    import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer;
    
    import javax.validation.ValidatorFactory;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ConcurrentMap;
    
    /**
     * 代理基于javax.validation.ValidatorFactory接口
     * 实际上代理org.hibernate.validator.internal.engine.ValidatorFactoryImpl类
     *
     * @author doflamingo
     */
    public class ValidatorFactoryImplProxy implements InvocationHandler {
    
        /**
         * 被代理对象
         */
        private ValidatorFactory target;
    
        /**
         * 被代理类的Class对象
         */
        private Class<? extends ValidatorFactory> targetClass;
    
        // 以下是要通过反射拿到的值
    
        private ValidationOrderGenerator validationOrderGenerator;
        private ConstraintCreationContext constraintCreationContext;
        private ValidatorFactoryScopedContext validatorFactoryScopedContext;
        private BeanMetaDataManager beanMetaDataManager;
    
    
        /**
         * 生成代理对象
         *
         * @param target
         * @return
         */
        public ValidatorFactory proxy(ValidatorFactory target) throws Throwable {
            this.target = target;
            this.targetClass = target.getClass();
    
            // 将targetClass相关私有属性通过反射机制拿到
            this.init();
    
            // 生成代理对象
            return (ValidatorFactory) Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    this
            );
        }
    
        /**
         * 将targetClass相关私有属性通过反射机制拿到
         *
         * @throws Throwable
         */
        private void init() throws Throwable {
            Field executableHelperField = targetClass.getDeclaredField("executableHelper");
            Field javaBeanHelperField = targetClass.getDeclaredField("javaBeanHelper");
            Field beanMetadataClassNormalizerField = targetClass.getDeclaredField("beanMetadataClassNormalizer");
            Field validationOrderGeneratorField = targetClass.getDeclaredField("validationOrderGenerator");
            Field constraintCreationContextField = targetClass.getDeclaredField("constraintCreationContext");
            Field validatorFactoryScopedContextField = targetClass.getDeclaredField("validatorFactoryScopedContext");
            Field methodValidationConfigurationField = targetClass.getDeclaredField("methodValidationConfiguration");
    
            // 访问打开
            executableHelperField.setAccessible(true);
            javaBeanHelperField.setAccessible(true);
            beanMetadataClassNormalizerField.setAccessible(true);
            validationOrderGeneratorField.setAccessible(true);
            constraintCreationContextField.setAccessible(true);
            validatorFactoryScopedContextField.setAccessible(true);
            methodValidationConfigurationField.setAccessible(true);
    
            ExecutableHelper executableHelper = (ExecutableHelper) executableHelperField.get(target);
            JavaBeanHelper javaBeanHelper = (JavaBeanHelper) javaBeanHelperField.get(target);
            BeanMetaDataClassNormalizer beanMetadataClassNormalizer = (BeanMetaDataClassNormalizer) beanMetadataClassNormalizerField.get(target);
            ValidationOrderGenerator validationOrderGenerator = (ValidationOrderGenerator) validationOrderGeneratorField.get(target);
            ConstraintCreationContext constraintCreationContext = (ConstraintCreationContext) constraintCreationContextField.get(target);
            ValidatorFactoryScopedContext validatorFactoryScopedContext = (ValidatorFactoryScopedContext) validatorFactoryScopedContextField.get(target);
            MethodValidationConfiguration methodValidationConfiguration = (MethodValidationConfiguration) methodValidationConfigurationField.get(target);
    
            // copy Map属性
            Field beanMetaDataManagersField = targetClass.getDeclaredField("beanMetaDataManagers");
            beanMetaDataManagersField.setAccessible(true);
            ConcurrentMap<BeanMetaDataManagerKey, BeanMetaDataManager> beanMetaDataManagers = this.beanMetaDataManager(beanMetaDataManagersField);
    
            Method buildMetaDataProviders = targetClass.getDeclaredMethod("buildMetaDataProviders");
            buildMetaDataProviders.setAccessible(true);
            List<MetaDataProvider> metaDataProviders = (List<MetaDataProvider>) buildMetaDataProviders.invoke(target);
    
            // 生成beanMetaDataManager
            BeanMetaDataManager beanMetaDataManager = beanMetaDataManagers.computeIfAbsent(
                    new BeanMetaDataManagerKey(validatorFactoryScopedContext.getParameterNameProvider(), constraintCreationContext.getValueExtractorManager(), methodValidationConfiguration),
                    key -> new BeanMetaDataManagerImpl(
                            constraintCreationContext,
                            executableHelper,
                            validatorFactoryScopedContext.getParameterNameProvider(),
                            javaBeanHelper,
                            beanMetadataClassNormalizer,
                            validationOrderGenerator,
                            metaDataProviders,
                            methodValidationConfiguration
                    )
            );
    
            this.validationOrderGenerator = validationOrderGenerator;
            this.constraintCreationContext = constraintCreationContext;
            this.validatorFactoryScopedContext = validatorFactoryScopedContext;
            this.beanMetaDataManager = beanMetaDataManager;
        }
    
        /**
         * 只处理getValidator()方法
         *
         * @param proxy
         * @param method
         * @param args
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            // 只处理了getValidator方法
            if ("getValidator".equals(method.getName())) {
    
                // 复制ValidatorImpl类命名为MyCopyValidatorImpl
                return new MyCopyValidatorImpl(
                        constraintCreationContext.getConstraintValidatorManager().getDefaultConstraintValidatorFactory(),
                        beanMetaDataManager,
                        this.constraintCreationContext.getValueExtractorManager(),
                        this.constraintCreationContext.getConstraintValidatorManager(),
                        validationOrderGenerator,
                        this.validatorFactoryScopedContext
                );
            }
    
            return method.invoke(this.target, args);
        }
    
        /**
         * copy私有内部类对象作为key的Map
         *
         * @param field
         * @return
         * @throws IllegalArgumentException
         * @throws IllegalAccessException
         */
        private ConcurrentMap<BeanMetaDataManagerKey, BeanMetaDataManager> beanMetaDataManager(Field field)
                throws IllegalArgumentException, IllegalAccessException {
            field.setAccessible(true);
            Map obj = (Map) field.get(target);
    
            // 放copy后的结果
            ConcurrentMap<BeanMetaDataManagerKey, BeanMetaDataManager> beanMetaDataManagers = new ConcurrentHashMap<>(obj.size());
    
            // 复制
            obj.forEach((k, v) -> {
                try {
                    Class s = k.getClass();
                    Field parameterNameProviderF = s.getDeclaredField("parameterNameProvider");
                    Field valueExtractorManagerF = s.getDeclaredField("valueExtractorManager");
                    Field methodValidationConfigurationF = s.getDeclaredField("methodValidationConfiguration");
    
                    parameterNameProviderF.setAccessible(true);
                    valueExtractorManagerF.setAccessible(true);
                    methodValidationConfigurationF.setAccessible(true);
    
                    ExecutableParameterNameProvider parameterNameProvider = (ExecutableParameterNameProvider) parameterNameProviderF.get(s);
                    ValueExtractorManager valueExtractorManager = (ValueExtractorManager) valueExtractorManagerF.get(s);
                    MethodValidationConfiguration methodValidationConfiguration = (MethodValidationConfiguration) methodValidationConfigurationF.get(s);
    
                    BeanMetaDataManagerKey beanMetaDataManagerKey = new BeanMetaDataManagerKey(parameterNameProvider, valueExtractorManager, methodValidationConfiguration);
    
                    beanMetaDataManagers.put(beanMetaDataManagerKey, (BeanMetaDataManager) v);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            return beanMetaDataManagers;
        }
    
        /**
         * ValidatorFactoryImpl中的内部类
         */
        private static class BeanMetaDataManagerKey {
            private final ExecutableParameterNameProvider parameterNameProvider;
            private final ValueExtractorManager valueExtractorManager;
            private final MethodValidationConfiguration methodValidationConfiguration;
            private final int hashCode;
    
            public BeanMetaDataManagerKey(ExecutableParameterNameProvider parameterNameProvider, ValueExtractorManager valueExtractorManager, MethodValidationConfiguration methodValidationConfiguration) {
                this.parameterNameProvider = parameterNameProvider;
                this.valueExtractorManager = valueExtractorManager;
                this.methodValidationConfiguration = methodValidationConfiguration;
                this.hashCode = buildHashCode(parameterNameProvider, valueExtractorManager, methodValidationConfiguration);
            }
    
            private static int buildHashCode(ExecutableParameterNameProvider parameterNameProvider, ValueExtractorManager valueExtractorManager, MethodValidationConfiguration methodValidationConfiguration) {
                final int prime = 31;
                int result = 1;
                result = prime * result + ((methodValidationConfiguration == null) ? 0 : methodValidationConfiguration.hashCode());
                result = prime * result + ((parameterNameProvider == null) ? 0 : parameterNameProvider.hashCode());
                result = prime * result + ((valueExtractorManager == null) ? 0 : valueExtractorManager.hashCode());
                return result;
            }
    
            @Override
            public int hashCode() {
                return hashCode;
            }
    
            @Override
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                if (obj == null) {
                    return false;
                }
                if (getClass() != obj.getClass()) {
                    return false;
                }
                BeanMetaDataManagerKey other = (BeanMetaDataManagerKey) obj;
    
                return methodValidationConfiguration.equals(other.methodValidationConfiguration) &&
                        parameterNameProvider.equals(other.parameterNameProvider) &&
                        valueExtractorManager.equals(other.valueExtractorManager);
            }
    
            @Override
            public String toString() {
                return "BeanMetaDataManagerKey [parameterNameProvider=" + parameterNameProvider + ", valueExtractorManager=" + valueExtractorManager
                        + ", methodValidationConfiguration=" + methodValidationConfiguration + "]";
            }
        }
    }
    

    测试类

    package com.mingo.exp.validate;
    
    import javax.validation.Validation;
    import javax.validation.Validator;
    import javax.validation.ValidatorFactory;
    
    /**
     * 运行方式:
     * 1、执行main
     *
     * @author doflamingo
     */
    public class HibernateValidateTest2 {
    
        // 1 ms = 1000000 ns
    
        public void test() throws Throwable {
    
            // 代理org.hibernate.validator.internal.engine.ValidatorFactoryImpl.getValidator()方法
            ValidatorFactory factory = new ValidatorFactoryImplProxy().proxy(Validation.buildDefaultValidatorFactory());
            // 得到的是MyCopyValidatorImpl类的对象
            Validator validator = factory.getValidator();
    
            // 只用一个对象作为测试
            ValidateTest1DTO t1 = new ValidateTest1DTO("A", 3.1415);
            validator.validate(t1);
        }
    
        public static void main(String[] args) throws Throwable {
            new HibernateValidateTest2().test();
        }
    
    }
    

    HibernateValidateTest2.main()运行结果

    StopWatch 'hibernate.validator.validate(T object, Class<?>... groups)时间测试': running time = 92530900 ns
    ---------------------------------------------
    ns         %     Task name
    ---------------------------------------------
    000042500  000%  第一段
    079955700  086%  第二段
    004400700  005%  第三段
    000042200  000%  第四段
    008089800  009%  第五段
    

    测试结果可以看出“第二段(也就是创建BeanMetaData对象)”比较耗时,验证了前面分析的原因。


    可见同一个Validator对象校验相同的实体类对象时,首次校验较为耗时。下面我测试下同一个Validator对象检验不同实体类对象

    测试下同一个Validator对象检验不同实体类对象耗时

    测试代码

    package com.mingo.exp.validate;
    
    import javax.validation.Validation;
    import javax.validation.Validator;
    import javax.validation.ValidatorFactory;
    
    /**
     * 运行方式:
     * 1、执行main
     *
     * @author doflamingo
     */
    public class HibernateValidateTest2 {
    
        // 1 ms = 1000000 ns
    
        public void test() throws Throwable {
    
            // 代理org.hibernate.validator.internal.engine.ValidatorFactoryImpl.getValidator()方法
            ValidatorFactory factory = new ValidatorFactoryImplProxy().proxy(Validation.buildDefaultValidatorFactory());
            // 得到的是MyCopyValidatorImpl类的对象
            Validator validator = factory.getValidator();
    
            // ValidateTest1DTO 两个对象
            ValidateTest1DTO t1 = new ValidateTest1DTO("A", 3.1415);
            ValidateTest1DTO t2 = new ValidateTest1DTO("B", 3.1415);
    
            validator.validate(t1);
            validator.validate(t2);
    
            // ValidateTest2DTO 两个对象
            ValidateTest2DTO t21 = new ValidateTest2DTO("A", 3.1415);
            ValidateTest2DTO t22 = new ValidateTest2DTO("B", 3.1415);
    
            validator.validate(t21);
            validator.validate(t22);
        }
    
        public static void main(String[] args) throws Throwable {
            new HibernateValidateTest2().test();
        }
    
    }
    

    HibernateValidateTest2.main()运行结果

    // t1对象
    StopWatch 'hibernate.validator.validate(T object, Class<?>... groups)时间测试': running time = 94788800 ns
    ---------------------------------------------
    ns         %     Task name
    ---------------------------------------------
    000031200  000%  第一段
    084693200  089%  第二段
    003510600  004%  第三段
    000038400  000%  第四段
    006515400  007%  第五段
    
    // t2对象
    StopWatch 'hibernate.validator.validate(T object, Class<?>... groups)时间测试': running time = 21900 ns
    ---------------------------------------------
    ns         %     Task name
    ---------------------------------------------
    000001900  009%  第一段
    000007500  034%  第二段
    000004600  021%  第三段
    000002900  013%  第四段
    000005000  023%  第五段
    
    // t21对象
    StopWatch 'hibernate.validator.validate(T object, Class<?>... groups)时间测试': running time = 8290300 ns
    ---------------------------------------------
    ns         %     Task name
    ---------------------------------------------
    000003200  000%  第一段
    008255900  100%  第二段
    000019600  000%  第三段
    000003800  000%  第四段
    000007800  000%  第五段
    
    // t22对象
    StopWatch 'hibernate.validator.validate(T object, Class<?>... groups)时间测试': running time = 43400 ns
    ---------------------------------------------
    ns         %     Task name
    ---------------------------------------------
    000003000  007%  第一段
    000004300  010%  第二段
    000027600  064%  第三段
    000003400  008%  第四段
    000005100  012%  第五段
    

    可见同一个Validator对象检验不同实体类对象首次都比较耗时,是按照Class对象来分类。


    结语

    • 如果要用hibernate.validator包校验数据约束,要注意只需生成一个Validator对象来作校验。
    • 对于实体类的首次校验耗时问题,一种解决思路是提前将数据写入org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.beanMetaDataCache缓存中。具体怎样写入可采用反射或者修改字节码方式,后文将实践。还有一种就是将要校验的对象类在项目启动后预先加载。

    原创 Doflamingo https://www.cnblogs.com/doflamingo
  • 相关阅读:
    apache和tomcat有什么不同,为什么要整合apache 和tomcat?
    servlet
    关于Spring配置文件xml文档的schema约束
    request对象和response对象
    多线程
    数据结构得到连续数据的手段java Enumeration
    程序员八荣八耻
    windows更改MySQL存储路径
    Tomcat源码学习(1)
    Tomcat源码学习(2)——启动过程分析
  • 原文地址:https://www.cnblogs.com/doflamingo/p/13126901.html
Copyright © 2020-2023  润新知