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缓存中。具体怎样写入可采用反射或者修改字节码方式,后文将实践。还有一种就是将要校验的对象类在项目启动后预先加载。

 

posted @ 2020-06-20 16:39  别名  阅读(1768)  评论(0编辑  收藏  举报