From aa1efedf4a470b8bb4fce2cbbea90d11bbc8f59b Mon Sep 17 00:00:00 2001 From: Benedikt Waldvogel Date: Thu, 31 Jul 2025 14:57:54 +0200 Subject: [PATCH] Support new Hibernate proxy class naming scheme in ClassUtils isProxy/isProxyClass The naming scheme changed in 6.6.12: https://hibernate.atlassian.net/browse/HHH-14694 --- build.gradle | 17 ++++-- gradle.lockfile | 16 +++++ .../de/cronn/reflection/util/ClassUtils.java | 6 +- .../cronn/reflection/util/ClassUtilsTest.java | 12 ++++ .../util/HibernateProxyTestUtil.java | 59 +++++++++++++++++++ 5 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 src/test/java/de/cronn/reflection/util/HibernateProxyTestUtil.java diff --git a/build.gradle b/build.gradle index 24b6461..f6b37cb 100644 --- a/build.gradle +++ b/build.gradle @@ -45,10 +45,6 @@ dependencies { } } -dependencyLocking { - lockAllConfigurations() -} - jacocoTestReport { reports { xml.required = true @@ -159,6 +155,9 @@ dependencies { testImplementation "org.javassist:javassist:latest.release" testImplementation "nl.jqno.equalsverifier:equalsverifier:latest.release" + testImplementation 'org.hibernate.orm:hibernate-core:latest.release' + testRuntimeOnly 'com.h2database:h2:latest.release' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation "org.openjdk.jmh:jmh-core:latest.release" @@ -167,4 +166,14 @@ dependencies { // no real transitive dependency but we use it to annotate method contracts to help the IDE understand the code compileOnly "org.jetbrains:annotations:latest.release" testCompileOnly "org.jetbrains:annotations:latest.release" + + dependencyLocking { + lockAllConfigurations() + } + + components.all { ComponentMetadataDetails details -> + if (details.id.version =~ /(?i).+(-|\.)(CANDIDATE|RC|BETA|ALPHA|PR|M\d+|CR\d+).*/) { + details.status = 'milestone' + } + } } diff --git a/gradle.lockfile b/gradle.lockfile index a6b4c0c..f9d1284 100644 --- a/gradle.lockfile +++ b/gradle.lockfile @@ -1,20 +1,36 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. +com.fasterxml:classmate:1.7.0=testRuntimeClasspath +com.h2database:h2:2.3.232=testRuntimeClasspath +com.sun.istack:istack-commons-runtime:4.1.2=testRuntimeClasspath +jakarta.activation:jakarta.activation-api:2.1.3=testRuntimeClasspath +jakarta.inject:jakarta.inject-api:2.0.1=testRuntimeClasspath +jakarta.persistence:jakarta.persistence-api:3.2.0=testCompileClasspath,testRuntimeClasspath +jakarta.transaction:jakarta.transaction-api:2.0.1=testCompileClasspath,testRuntimeClasspath jakarta.validation:jakarta.validation-api:3.1.1=testCompileClasspath,testRuntimeClasspath +jakarta.xml.bind:jakarta.xml.bind-api:4.0.2=testRuntimeClasspath net.bytebuddy:byte-buddy-agent:1.17.5=testCompileClasspath,testRuntimeClasspath net.bytebuddy:byte-buddy:1.17.6=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath net.sf.jopt-simple:jopt-simple:5.0.4=testCompileClasspath,testRuntimeClasspath nl.jqno.equalsverifier:equalsverifier:4.0.7=testCompileClasspath,testRuntimeClasspath +org.antlr:antlr4-runtime:4.13.2=testRuntimeClasspath org.apache.commons:commons-lang3:3.18.0=testCompileClasspath,testRuntimeClasspath org.apache.commons:commons-math3:3.6.1=testCompileClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath org.assertj:assertj-core:3.27.3=testCompileClasspath,testRuntimeClasspath +org.eclipse.angus:angus-activation:2.0.2=testRuntimeClasspath +org.glassfish.jaxb:jaxb-core:4.0.5=testRuntimeClasspath +org.glassfish.jaxb:jaxb-runtime:4.0.5=testRuntimeClasspath +org.glassfish.jaxb:txw2:4.0.5=testRuntimeClasspath +org.hibernate.models:hibernate-models:1.0.0=testRuntimeClasspath +org.hibernate.orm:hibernate-core:7.0.8.Final=testCompileClasspath,testRuntimeClasspath org.jacoco:org.jacoco.agent:0.8.13=jacocoAgent,jacocoAnt org.jacoco:org.jacoco.ant:0.8.13=jacocoAnt org.jacoco:org.jacoco.core:0.8.13=jacocoAnt org.jacoco:org.jacoco.report:0.8.13=jacocoAnt org.javassist:javassist:3.30.2-GA=testCompileClasspath,testRuntimeClasspath +org.jboss.logging:jboss-logging:3.6.1.Final=testRuntimeClasspath org.jetbrains:annotations:26.0.2=compileClasspath,testCompileClasspath org.junit.jupiter:junit-jupiter-api:5.13.4=testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:5.13.4=testRuntimeClasspath diff --git a/src/main/java/de/cronn/reflection/util/ClassUtils.java b/src/main/java/de/cronn/reflection/util/ClassUtils.java index 44c7cb0..1491d70 100644 --- a/src/main/java/de/cronn/reflection/util/ClassUtils.java +++ b/src/main/java/de/cronn/reflection/util/ClassUtils.java @@ -19,7 +19,8 @@ public final class ClassUtils { private static final String JAVASSIST_CLASS_SEPARATOR = "$$"; private static final String BYTE_BUDDY_CLASS_SEPARATOR = "$ByteBuddy$"; - private static final String HIBERNATE_PROXY_CLASS_SEPARATOR = "$HibernateProxy$"; + private static final String HIBERNATE_OLD_PROXY_CLASS_SEPARATOR = "$HibernateProxy$"; + private static final String HIBERNATE_NEW_PROXY_CLASS_SUFFIX = "$HibernateProxy"; private static final ClassValue> methodsSignaturesCache = ClassValues.create(ClassUtils::getAllDeclaredMethodSignatures); @@ -167,7 +168,8 @@ public static boolean isProxyClass(Class clazz) { static boolean matchesWellKnownProxyClassNamePattern(String className) { return className.contains(BYTE_BUDDY_CLASS_SEPARATOR) || className.contains(JAVASSIST_CLASS_SEPARATOR) - || className.contains(HIBERNATE_PROXY_CLASS_SEPARATOR); + || className.contains(HIBERNATE_OLD_PROXY_CLASS_SEPARATOR) + || className.endsWith(HIBERNATE_NEW_PROXY_CLASS_SUFFIX); } public static boolean haveSameSignature(Method oneMethod, Method otherMethod) { diff --git a/src/test/java/de/cronn/reflection/util/ClassUtilsTest.java b/src/test/java/de/cronn/reflection/util/ClassUtilsTest.java index f38ace3..4f75ba6 100644 --- a/src/test/java/de/cronn/reflection/util/ClassUtilsTest.java +++ b/src/test/java/de/cronn/reflection/util/ClassUtilsTest.java @@ -14,6 +14,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.hibernate.proxy.HibernateProxy; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -256,6 +257,17 @@ void testIsProxyClass() throws Exception { assertThat(ClassUtils.isProxyClass(null)).isFalse(); } + @Test + void testIsProxyClass_Hibernate() throws Exception { + HibernateProxyTestUtil.runWithHibernateProxy(personProxy -> { + assertThat(personProxy).isInstanceOf(HibernateProxy.class); + assertThat(personProxy.getClass().getSimpleName()).endsWith("$Person$HibernateProxy"); + + assertThat(ClassUtils.isProxy(personProxy)).isTrue(); + assertThat(ClassUtils.isProxyClass(personProxy.getClass())).isTrue(); + }); + } + @Test void testHasMethodWithSameSignature_happyPath_shouldMatchMethodSignature_whenReturnTypeAndNameAndParametersAreEqual() throws Exception { Method targetMethod = findMethod(SomeClass.class, "doWork", int.class); diff --git a/src/test/java/de/cronn/reflection/util/HibernateProxyTestUtil.java b/src/test/java/de/cronn/reflection/util/HibernateProxyTestUtil.java new file mode 100644 index 0000000..12e2805 --- /dev/null +++ b/src/test/java/de/cronn/reflection/util/HibernateProxyTestUtil.java @@ -0,0 +1,59 @@ +package de.cronn.reflection.util; + +import java.util.function.Consumer; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +class HibernateProxyTestUtil { + + @Entity + public static class Person { + + @Id + @GeneratedValue + private Long id; + + private String name; + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + static void runWithHibernateProxy(Consumer hibernateProxyConsumer) { + StandardServiceRegistry registry = new StandardServiceRegistryBuilder() + .applySetting("hibernate.connection.driver_class", "org.h2.Driver") + .applySetting("hibernate.connection.url", "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1") + .applySetting("hibernate.dialect", "org.hibernate.dialect.H2Dialect") + .applySetting("hibernate.hbm2ddl.auto", "create-drop") + .build(); + + SessionFactory sessionFactory = new MetadataSources(registry) + .addAnnotatedClass(Person.class) + .buildMetadata() + .buildSessionFactory(); + + try (Session session = sessionFactory.openSession()) { + Person personProxy = session.getReference(Person.class, 123L); + hibernateProxyConsumer.accept(personProxy); + } finally { + sessionFactory.close(); + } + } +}