diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index 2aa61f22adc2fc..eea8415ffa64a0 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -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; @@ -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; @@ -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; @@ -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; @@ -612,6 +620,57 @@ 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. + *

+ * For example, the generated bytecode for an entity {@code com.example.MyEntity} will look something like: + *

+     * {@code
+     * package com.example;
+     *
+     * import org.hibernate.bytecode.spi.ReflectionOptimizer;
+     *
+     * public class MyEntity$QuarkusInstantiator implements ReflectionOptimizer.InstantiationOptimizer {
+     *     public Object newInstance() {
+     *         return new MyEntity();
+     *     }
+     * }
+     * }
+     * 
+ */ + @BuildStep + void instantiationOptimizers(JpaModelBuildItem jpaModel, JpaModelIndexBuildItem index, + BuildProducer generatedClasses, + List additionalJpaModelBuildItems, + List persistenceUnitDescriptorBuildItems) { + GeneratedClassGizmoAdaptor classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, true); + // Collect all managed class names to generate optimizers for + Set 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, diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/QuarkusRuntimeBytecodeProviderInitiator.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/QuarkusRuntimeBytecodeProviderInitiator.java index 0b164364d7e419..8bfaf1769f6f46 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/QuarkusRuntimeBytecodeProviderInitiator.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/QuarkusRuntimeBytecodeProviderInitiator.java @@ -9,6 +9,7 @@ import io.quarkus.hibernate.orm.runtime.customized.QuarkusRuntimeProxyFactoryFactory; public final class QuarkusRuntimeBytecodeProviderInitiator implements StandardServiceInitiator { + public static final String INSTANTIATOR_SUFFIX = "$QuarkusInstantiator"; private final QuarkusRuntimeProxyFactoryFactory statefulProxyFactory; diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/RuntimeBytecodeProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/RuntimeBytecodeProvider.java index cde9b0b1093183..939c36cced45f5 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/RuntimeBytecodeProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/RuntimeBytecodeProvider.java @@ -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; @@ -35,6 +37,26 @@ public ReflectionOptimizer getReflectionOptimizer( @Override public ReflectionOptimizer getReflectionOptimizer(Class clazz, Map 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; }