Skip to content

Commit

Permalink
Generate Hibernate ORM InstantiationOptimizers to avoid reflection
Browse files Browse the repository at this point in the history
  • Loading branch information
mbladel committed Oct 8, 2024
1 parent 6543030 commit f44a690
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 0 deletions.
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,58 @@ 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) {
GeneratedClassGizmoAdaptor classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, true);
// Collect all managed class names to generate optimizers for
Set<String> managedClassAndPackageNames = new HashSet<>(jpaModel.getManagedClassNames());
for (PersistenceUnitDescriptorBuildItem pud : persistenceUnitDescriptorBuildItems) {
managedClassAndPackageNames.addAll(pud.getManagedClassNames());
}
for (AdditionalJpaModelBuildItem additionalJpaModelBuildItem : additionalJpaModelBuildItems) {
managedClassAndPackageNames.add(additionalJpaModelBuildItem.getClassName());
}
// Generate InstantiationOptimizers for all managed classes
for (String i : managedClassAndPackageNames) {
ClassInfo classInfo = index.getIndex().getClassByName(i);
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

0 comments on commit f44a690

Please sign in to comment.