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 dfc55d7c0b4d..d9680e775d86 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -103,11 +103,10 @@ public static BeanRegistrationAotContribution processAheadOfTime(RegisteredBean } Class beanClass = registeredBean.getBeanClass(); - Set> visitedClasses = new HashSet<>(); Set> validatedClasses = new HashSet<>(); Set>> constraintValidatorClasses = new HashSet<>(); - processAheadOfTime(beanClass, visitedClasses, validatedClasses, constraintValidatorClasses); + processAheadOfTime(beanClass, new HashSet<>(), validatedClasses, constraintValidatorClasses); if (!validatedClasses.isEmpty() || !constraintValidatorClasses.isEmpty()) { return new AotContribution(validatedClasses, constraintValidatorClasses); @@ -118,10 +117,11 @@ public static BeanRegistrationAotContribution processAheadOfTime(RegisteredBean private static void processAheadOfTime(Class clazz, Set> visitedClasses, Set> validatedClasses, Set>> constraintValidatorClasses) { + Assert.notNull(validator, "Validator cannot be null"); + if (!visitedClasses.add(clazz)) { return; } - Assert.notNull(validator, "Validator can't be null"); BeanDescriptor descriptor; try { 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 bbcdf3e4b707..b3f3de83cf85 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import jakarta.validation.Constraint; import jakarta.validation.ConstraintValidator; @@ -127,7 +126,7 @@ void shouldProcessTransitiveGenericTypeLevelConstraint() { } @ParameterizedTest // gh-33936 - @ValueSource(classes = {BeanWithIterable.class, BeanWithMap.class, BeanWithOptional.class}) + @ValueSource(classes = {BeanWithRecursiveIterable.class, BeanWithRecursiveMap.class, BeanWithRecursiveOptional.class}) void shouldProcessRecursiveGenericsWithoutInfiniteRecursion(Class beanClass) { process(beanClass); assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).hasSize(1); @@ -258,16 +257,16 @@ public void setExclude(List exclude) { } } - static class BeanWithIterable { - private final Iterable beans = Set.of(); + static class BeanWithRecursiveIterable { + Iterable iterable; } - static class BeanWithMap { - private final Map beans = Map.of(); + static class BeanWithRecursiveMap { + Map map; } - static class BeanWithOptional { - private final Optional beans = Optional.empty(); + static class BeanWithRecursiveOptional { + Optional optional; } }