From a4af503e9981c359e7d2a549298539341eadcb90 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 hints harvesting for validation constraint with missing dependencies --- ...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 d9680e775d86..11d18ad5da6c 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 @@ private static void processAheadOfTime(Class clazz, Set> visitedClas 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 b3f3de83cf85..a062786daa93 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.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 @@ void shouldProcessRecursiveGenericsWithoutInfiniteRecursion(Class beanClass) .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 @@ static class BeanWithRecursiveOptional { 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 b060d6ccde7bdbec71de0b5c06b2e49c6caa65bd Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Sat, 23 Nov 2024 20:25:24 +0100 Subject: [PATCH 2/2] Skip infrastructure beans --- ...alidationBeanRegistrationAotProcessor.java | 3 ++- ...tionBeanRegistrationAotProcessorTests.java | 22 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 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 11d18ad5da6c..88ed693cda9b 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 @@ -43,6 +43,7 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; import org.springframework.beans.factory.aot.BeanRegistrationCode; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.core.KotlinDetector; import org.springframework.core.ResolvableType; @@ -98,7 +99,7 @@ private static Validator getValidatorIfAvailable() { @Nullable public static BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { - if (validator == null) { + if (validator == null || BeanDefinition.ROLE_INFRASTRUCTURE == registeredBean.getMergedBeanDefinition().getRole()) { return null; } 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 a062786daa93..f0925cb8284e 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 @@ -41,9 +41,9 @@ import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; +import org.springframework.beans.factory.config.BeanDefinition; 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; @@ -56,6 +56,9 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.springframework.beans.factory.config.BeanDefinition.ROLE_APPLICATION; +import static org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE; +import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition; /** * Tests for {@link BeanValidationBeanRegistrationAotProcessor}. @@ -143,17 +146,28 @@ void shouldSkipConstraintWithMissingDependency() throws Exception { assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).isEmpty(); } + @Test // gh-33940 + void shouldSkipInfrastructureBean() { + process(EmptyClass.class, ROLE_INFRASTRUCTURE); + assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).isEmpty(); + } + private void process(Class beanClass) { - BeanRegistrationAotContribution contribution = createContribution(beanClass); + process(beanClass, ROLE_APPLICATION); + } + + private void process(Class beanClass, int role) { + BeanRegistrationAotContribution contribution = createContribution(beanClass, role); if (contribution != null) { contribution.applyTo(this.generationContext, mock()); } } @Nullable - private BeanRegistrationAotContribution createContribution(Class beanClass) { + private BeanRegistrationAotContribution createContribution(Class beanClass, int role) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.registerBeanDefinition(beanClass.getName(), new RootBeanDefinition(beanClass)); + BeanDefinition beanDefinition = rootBeanDefinition(beanClass).setRole(role).getBeanDefinition(); + beanFactory.registerBeanDefinition(beanClass.getName(), beanDefinition); return this.processor.processAheadOfTime(RegisteredBean.of(beanFactory, beanClass.getName())); }