From 9b0253e117049c264f9ca0737eca45319f23b805 Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Sat, 23 Nov 2024 16:10:47 +0100 Subject: [PATCH 1/2] Skip runtime hint registration for constraint with missing dependencies Prior to this commit, AOT processing for bean validation failed with a NoClassDefFoundError for constraints with missing dependencies. With this commit, the processing no longer fails, and a warning is logged instead. See gh-33940 Closes gh-33949 Co-authored-by: Sam Brannen --- ...alidationBeanRegistrationAotProcessor.java | 9 ++--- ...tionBeanRegistrationAotProcessorTests.java | 36 +++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessor.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessor.java index d9680e775d..11d18ad5da 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessor.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessor.java @@ -127,18 +127,19 @@ class BeanValidationBeanRegistrationAotProcessor implements BeanRegistrationAotP try { descriptor = validator.getConstraintsForClass(clazz); } - catch (RuntimeException ex) { + catch (RuntimeException | LinkageError ex) { + String className = clazz.getName(); if (KotlinDetector.isKotlinType(clazz) && ex instanceof ArrayIndexOutOfBoundsException) { // See https://hibernate.atlassian.net/browse/HV-1796 and https://youtrack.jetbrains.com/issue/KT-40857 - logger.warn("Skipping validation constraint hint inference for class " + clazz + + logger.warn("Skipping validation constraint hint inference for class " + className + " due to an ArrayIndexOutOfBoundsException at validator level"); } else if (ex instanceof TypeNotPresentException) { logger.debug("Skipping validation constraint hint inference for class " + - clazz + " due to a TypeNotPresentException at validator level: " + ex.getMessage()); + className + " due to a TypeNotPresentException at validator level: " + ex.getMessage()); } else { - logger.warn("Skipping validation constraint hint inference for class " + clazz, ex); + logger.warn("Skipping validation constraint hint inference for class " + className, ex); } return; } diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessorTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessorTests.java index b3f3de83cf..a062786daa 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessorTests.java @@ -44,6 +44,7 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.OverridingClassLoader; import org.springframework.lang.Nullable; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; @@ -134,6 +135,14 @@ class BeanValidationBeanRegistrationAotProcessorTests { .withMemberCategory(MemberCategory.DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); } + @Test // gh-33940 + void shouldSkipConstraintWithMissingDependency() throws Exception { + FilteringClassLoader classLoader = new FilteringClassLoader(getClass().getClassLoader()); + Class beanClass = classLoader.loadClass(ConstraintWithMissingDependency.class.getName()); + process(beanClass); + assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).isEmpty(); + } + private void process(Class beanClass) { BeanRegistrationAotContribution contribution = createContribution(beanClass); if (contribution != null) { @@ -269,4 +278,31 @@ class BeanValidationBeanRegistrationAotProcessorTests { Optional optional; } + static class ConstraintWithMissingDependency { + + private final Filtered filtered = new Filtered(); + } + + static class Filtered {} + + static class FilteringClassLoader extends OverridingClassLoader { + + FilteringClassLoader(ClassLoader parent) { + super(parent); + } + + @Override + protected boolean isEligibleForOverriding(String className) { + return className.startsWith(BeanValidationBeanRegistrationAotProcessorTests.class.getName()); + } + + @Override + protected Class loadClassForOverriding(String name) throws ClassNotFoundException { + if (name.contains("Filtered")) { + throw new NoClassDefFoundError(name); + } + return super.loadClassForOverriding(name); + } + } + } From ea3bd7ae0c6e07e6f5fe7303d757efd203c0eca9 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:35:13 +0100 Subject: [PATCH 2/2] Polish BeanValidationBeanRegistrationAotProcessor[Tests] The log message for a NoClassDefFoundError is now a DEBUG level message handled like a TypeNotPresentException and similar to the following. DEBUG: Skipping validation constraint hint inference for class org.example.CustomConstraint due to a NoClassDefFoundError for com.example.MissingType See gh-33949 --- ...ValidationBeanRegistrationAotProcessor.java | 18 ++++++++++++------ ...ationBeanRegistrationAotProcessorTests.java | 12 ++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessor.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessor.java index 11d18ad5da..57cddf3f56 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessor.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessor.java @@ -131,15 +131,21 @@ class BeanValidationBeanRegistrationAotProcessor implements BeanRegistrationAotP String className = clazz.getName(); if (KotlinDetector.isKotlinType(clazz) && ex instanceof ArrayIndexOutOfBoundsException) { // See https://hibernate.atlassian.net/browse/HV-1796 and https://youtrack.jetbrains.com/issue/KT-40857 - logger.warn("Skipping validation constraint hint inference for class " + className + - " due to an ArrayIndexOutOfBoundsException at validator level"); + if (logger.isWarnEnabled()) { + logger.warn("Skipping validation constraint hint inference for class " + className + + " due to an ArrayIndexOutOfBoundsException at validator level"); + } } - else if (ex instanceof TypeNotPresentException) { - logger.debug("Skipping validation constraint hint inference for class " + - className + " due to a TypeNotPresentException at validator level: " + ex.getMessage()); + else if (ex instanceof TypeNotPresentException || ex instanceof NoClassDefFoundError) { + if (logger.isDebugEnabled()) { + logger.debug("Skipping validation constraint hint inference for class %s due to a %s for %s" + .formatted(className, ex.getClass().getSimpleName(), ex.getMessage())); + } } else { - logger.warn("Skipping validation constraint hint inference for class " + className, ex); + if (logger.isWarnEnabled()) { + logger.warn("Skipping validation constraint hint inference for class " + className, ex); + } } return; } diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessorTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessorTests.java index a062786daa..27be1d5fbe 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessorTests.java @@ -137,7 +137,7 @@ class BeanValidationBeanRegistrationAotProcessorTests { @Test // gh-33940 void shouldSkipConstraintWithMissingDependency() throws Exception { - FilteringClassLoader classLoader = new FilteringClassLoader(getClass().getClassLoader()); + MissingDependencyClassLoader classLoader = new MissingDependencyClassLoader(getClass().getClassLoader()); Class beanClass = classLoader.loadClass(ConstraintWithMissingDependency.class.getName()); process(beanClass); assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).isEmpty(); @@ -280,14 +280,14 @@ class BeanValidationBeanRegistrationAotProcessorTests { static class ConstraintWithMissingDependency { - private final Filtered filtered = new Filtered(); + MissingType missingType; } - static class Filtered {} + static class MissingType {} - static class FilteringClassLoader extends OverridingClassLoader { + static class MissingDependencyClassLoader extends OverridingClassLoader { - FilteringClassLoader(ClassLoader parent) { + MissingDependencyClassLoader(ClassLoader parent) { super(parent); } @@ -298,7 +298,7 @@ class BeanValidationBeanRegistrationAotProcessorTests { @Override protected Class loadClassForOverriding(String name) throws ClassNotFoundException { - if (name.contains("Filtered")) { + if (name.contains("MissingType")) { throw new NoClassDefFoundError(name); } return super.loadClassForOverriding(name);