Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate Hibernate ORM InstantiationOptimizers to avoid reflection #43767

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
import static io.quarkus.hibernate.orm.deployment.HibernateConfigUtil.firstPresent;
import static io.quarkus.hibernate.orm.runtime.service.bytecodeprovider.QuarkusRuntimeBytecodeProviderInitiator.INSTANTIATOR_SUFFIX;
import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_MODE;
import static org.hibernate.cfg.AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES;
import static org.hibernate.cfg.AvailableSettings.USE_QUERY_CACHE;
import static org.hibernate.cfg.AvailableSettings.USE_SECOND_LEVEL_CACHE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;

import java.io.IOException;
import java.net.URL;
Expand Down Expand Up @@ -44,6 +46,7 @@
import org.hibernate.boot.archive.scan.spi.ClassDescriptor;
import org.hibernate.boot.archive.scan.spi.PackageDescriptor;
import org.hibernate.boot.beanvalidation.BeanValidationIntegrator;
import org.hibernate.bytecode.spi.ReflectionOptimizer;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.id.SequenceMismatchStrategy;
import org.hibernate.integrator.spi.Integrator;
Expand Down Expand Up @@ -80,6 +83,7 @@
import io.quarkus.datasource.common.runtime.DatabaseKind;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.GeneratedClassGizmoAdaptor;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
Expand Down Expand Up @@ -108,6 +112,10 @@
import io.quarkus.deployment.recording.RecorderContext;
import io.quarkus.deployment.util.IoUtil;
import io.quarkus.deployment.util.ServiceUtil;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.hibernate.orm.PersistenceUnit;
import io.quarkus.hibernate.orm.deployment.HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy;
import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationRuntimeConfiguredBuildItem;
Expand Down Expand Up @@ -612,6 +620,65 @@ public HibernateModelClassCandidatesForFieldAccessBuildItem candidatesForFieldAc
return new HibernateModelClassCandidatesForFieldAccessBuildItem(jpaModel.getManagedClassNames());
}

/**
* Generate {@link org.hibernate.bytecode.spi.ReflectionOptimizer.InstantiationOptimizer}s for
* managed classes to avoid using reflection when Hibernate creates new instances.
* <p>
* For example, the generated bytecode for an entity {@code com.example.MyEntity} will look something like:
*
* <pre>
* {@code
* package com.example;
*
* import org.hibernate.bytecode.spi.ReflectionOptimizer;
*
* public class MyEntity$QuarkusInstantiator implements ReflectionOptimizer.InstantiationOptimizer {
* public Object newInstance() {
* return new MyEntity();
* }
* }
* }
* </pre>
*/
@BuildStep
void instantiationOptimizers(JpaModelBuildItem jpaModel, JpaModelIndexBuildItem index,
BuildProducer<GeneratedClassBuildItem> generatedClasses,
List<AdditionalJpaModelBuildItem> additionalJpaModelBuildItems,
List<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptorBuildItems) {
// Collect all managed class names to generate optimizers for
Set<String> managedClassAndPackageNames = new HashSet<>(jpaModel.getManagedClassNames());
for (PersistenceUnitDescriptorBuildItem pud : persistenceUnitDescriptorBuildItems) {
managedClassAndPackageNames.addAll(pud.getManagedClassNames());
}
// Generate instantiation optimizers for all managed classes
GeneratedClassGizmoAdaptor applicationClassOutput = new GeneratedClassGizmoAdaptor(generatedClasses, true);
for (String managedClass : managedClassAndPackageNames) {
createInstantiator(managedClass, index, applicationClassOutput);
}
// Also create instantiators for additional build items, but assume these are not application classes
GeneratedClassGizmoAdaptor classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, false);
for (AdditionalJpaModelBuildItem additionalJpaModelBuildItem : additionalJpaModelBuildItems) {
createInstantiator(additionalJpaModelBuildItem.getClassName(), index, classOutput);
}
}

private static void createInstantiator(String mappedClass, JpaModelIndexBuildItem index,
GeneratedClassGizmoAdaptor classOutput) {
ClassInfo classInfo = index.getIndex().getClassByName(mappedClass);
if (classInfo != null && !classInfo.isAbstract() && classInfo.hasNoArgsConstructor()) {
String className = classInfo.name() + INSTANTIATOR_SUFFIX;
try (ClassCreator classCreator = ClassCreator.builder().classOutput(classOutput)
.interfaces(ReflectionOptimizer.InstantiationOptimizer.class).className(className).build()) {
MethodCreator method = classCreator
.getMethodCreator("newInstance", Object.class)
.setModifiers(ACC_PUBLIC);
ResultHandle constructorHandle = method
.newInstance(MethodDescriptor.ofConstructor(classInfo.name().toString()));
method.returnValue(constructorHandle);
}
}
}

@BuildStep
@Record(RUNTIME_INIT)
public void build(HibernateOrmRecorder recorder, HibernateOrmConfig hibernateOrmConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.quarkus.hibernate.orm.runtime.customized.QuarkusRuntimeProxyFactoryFactory;

public final class QuarkusRuntimeBytecodeProviderInitiator implements StandardServiceInitiator<BytecodeProvider> {
public static final String INSTANTIATOR_SUFFIX = "$QuarkusInstantiator";

private final QuarkusRuntimeProxyFactoryFactory statefulProxyFactory;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.hibernate.orm.runtime.service.bytecodeprovider;

import static io.quarkus.hibernate.orm.runtime.service.bytecodeprovider.QuarkusRuntimeBytecodeProviderInitiator.INSTANTIATOR_SUFFIX;

import java.util.Map;

import org.hibernate.bytecode.enhance.spi.EnhancementContext;
Expand Down Expand Up @@ -35,6 +37,26 @@ public ReflectionOptimizer getReflectionOptimizer(

@Override
public ReflectionOptimizer getReflectionOptimizer(Class<?> clazz, Map<String, PropertyAccess> propertyAccessMap) {
try {
Class<?> instantiatorClass = Class.forName(clazz.getName() + INSTANTIATOR_SUFFIX, true,
Thread.currentThread().getContextClassLoader());
ReflectionOptimizer.InstantiationOptimizer optimizer = (ReflectionOptimizer.InstantiationOptimizer) instantiatorClass
.getDeclaredConstructor().newInstance();
return new ReflectionOptimizer() {
@Override
public InstantiationOptimizer getInstantiationOptimizer() {
return optimizer;
}

@Override
public AccessOptimizer getAccessOptimizer() {
return null;
}
};
} catch (Exception e) {
// Some mapped classes will not have an optimizer, e.g. abstract entities or classes missing a no-args constructor.
// If we didn't find the optimizer class, just ignore the exception and return null
}
return null;
}

Expand Down
Loading