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緩存中。具體怎樣寫入可采用反射或者修改字節碼方式,后文將實踐。還有一種就是將要校驗的對象類在項目啟動后預先加載。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM