Reuse generated entity instantiators from ClassGeneratingEntityInstantiator instances.

We now reuse generated ObjectInstantiator classes instead of attempting to redefine classes with the same name. Redefinition leads to LinkageError and thus to the use of reflection-based instantiators and an increased allocation rate due to the failed attempt of class redeclaration.

Closes: #2446.
This commit is contained in:
Mark Paluch
2021-09-09 15:18:30 +02:00
parent 345e18c2ac
commit bd280f8dfd
2 changed files with 46 additions and 3 deletions

View File

@@ -124,6 +124,13 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
try {
return doCreateEntityInstantiator(entity);
} catch (Throwable ex) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
String.format("Cannot create entity instantiator for %s. Falling back to ReflectionEntityInstantiator.",
entity.getName()),
ex);
}
return ReflectionEntityInstantiator.INSTANCE;
}
}
@@ -336,12 +343,22 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
@Nullable PreferredConstructor<?, ?> constructor) {
String className = generateClassName(entity);
Class<?> type = entity.getType();
ClassLoader classLoader = type.getClassLoader();
if (ClassUtils.isPresent(className, classLoader)) {
try {
return ClassUtils.forName(className, classLoader);
} catch (Exception o_O) {
throw new IllegalStateException(o_O);
}
}
byte[] bytecode = generateBytecode(className, entity, constructor);
Class<?> type = entity.getType();
try {
return ReflectUtils.defineClass(className, bytecode, type.getClassLoader(), type.getProtectionDomain(), type);
return ReflectUtils.defineClass(className, bytecode, classLoader, type.getProtectionDomain(), type);
} catch (Exception e) {
throw new IllegalStateException(e);
}

View File

@@ -23,6 +23,7 @@ import static org.springframework.data.util.ClassTypeInformation.from;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;
import org.junit.jupiter.api.Test;
@@ -39,6 +40,8 @@ import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.ObjectInstantiator;
import org.springframework.data.mapping.model.ClassGeneratingEntityInstantiatorUnitTests.Outer.Inner;
import org.springframework.data.util.TypeInformation;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ReflectionUtils;
/**
@@ -210,6 +213,9 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
assertThat(instance).isInstanceOf(ObjCtorNoArgs.class);
assertThat(((ObjCtorNoArgs) instance).ctorInvoked).isTrue();
});
ClassGeneratingEntityInstantiator classGeneratingEntityInstantiator = new ClassGeneratingEntityInstantiator();
classGeneratingEntityInstantiator.createInstance(entity, provider);
}
@Test // DATACMNS-578, DATACMNS-1126
@@ -360,6 +366,26 @@ class ClassGeneratingEntityInstantiatorUnitTests<P extends PersistentProperty<P>
assertThat(this.instance.shouldUseReflectionEntityInstantiator(entity)).isTrue();
}
@Test // GH-2446
void shouldReuseGeneratedClasses() {
prepareMocks(ProtectedInnerClass.class);
this.instance.createInstance(entity, provider);
ClassGeneratingEntityInstantiator instantiator = new ClassGeneratingEntityInstantiator();
instantiator.createInstance(entity, provider);
Map<TypeInformation<?>, EntityInstantiator> first = (Map<TypeInformation<?>, EntityInstantiator>) ReflectionTestUtils
.getField(this.instance, "entityInstantiators");
Map<TypeInformation<?>, EntityInstantiator> second = (Map<TypeInformation<?>, EntityInstantiator>) ReflectionTestUtils
.getField(instantiator, "entityInstantiators");
assertThat(first.get(null)).isNotNull().isNotInstanceOf(Enum.class);
assertThat(second.get(null)).isNotNull().isNotInstanceOf(Enum.class);
}
@Test // DATACMNS-1422
void shouldUseReflectionIfFrameworkTypesNotVisible() throws Exception {