Skip to content

Support new Hibernate proxy class naming scheme in ClassUtils isProxy/isProxyClass #27

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

Merged
merged 1 commit into from
Jul 31, 2025
Merged
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
17 changes: 13 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,6 @@ dependencies {
}
}

dependencyLocking {
lockAllConfigurations()
}

jacocoTestReport {
reports {
xml.required = true
Expand Down Expand Up @@ -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"
Expand All @@ -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'
}
}
}
16 changes: 16 additions & 0 deletions gradle.lockfile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/de/cronn/reflection/util/ClassUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Set<MethodSignature>> methodsSignaturesCache = ClassValues.create(ClassUtils::getAllDeclaredMethodSignatures);

Expand Down Expand Up @@ -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) {
Expand Down
12 changes: 12 additions & 0 deletions src/test/java/de/cronn/reflection/util/ClassUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
59 changes: 59 additions & 0 deletions src/test/java/de/cronn/reflection/util/HibernateProxyTestUtil.java
Original file line number Diff line number Diff line change
@@ -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<Person> 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();
}
}
}
Loading