前段時間對老項目做性能優化時,發現用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

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