From 1d6690cdbbaebe32ba3208420a976e0b1964d99f Mon Sep 17 00:00:00 2001 From: Olga Maciaszek-Sharma Date: Thu, 16 Mar 2023 13:01:43 +0100 Subject: [PATCH] Add hints for Configurable objects supertypes and their generics. (#2890) --- spring-cloud-gateway-server/pom.xml | 5 + ...onfigurableHintsRegistrationProcessor.java | 27 +++++- ...urableHintsRegistrationProcessorTests.java | 97 +++++++++++++++++++ 3 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessorTests.java diff --git a/spring-cloud-gateway-server/pom.xml b/spring-cloud-gateway-server/pom.xml index b4a57709..f4262fa9 100644 --- a/spring-cloud-gateway-server/pom.xml +++ b/spring-cloud-gateway-server/pom.xml @@ -235,6 +235,11 @@ 1.20 test + + org.springframework + spring-core-test + test + diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessor.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessor.java index 3dab2333..a2d525c1 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessor.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2023 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. @@ -92,18 +92,35 @@ class ConfigurableHintsRegistrationProcessor implements BeanFactoryInitializatio private static Set> getConfigurableTypes() { Set> classesToAdd = getClassesToAdd(); Set> genericsToAdd = new HashSet<>(); + Set> superTypes = new HashSet<>(); for (Class clazz : classesToAdd) { ResolvableType resolvableType = ResolvableType.forType(clazz); - if (resolvableType.getSuperType().hasGenerics()) { - genericsToAdd.addAll(Arrays.stream(resolvableType.getSuperType().getGenerics()) - .map(ResolvableType::toClass).collect(Collectors.toSet())); - } + addGenericsForClass(genericsToAdd, resolvableType); + addSuperTypesForClass(resolvableType, superTypes, genericsToAdd); } classesToAdd.addAll(genericsToAdd); + classesToAdd.addAll(superTypes); return classesToAdd.stream().filter(Objects::nonNull).collect(Collectors.toSet()); } + private static void addGenericsForClass(Set> genericsToAdd, ResolvableType resolvableType) { + if (resolvableType.getSuperType().hasGenerics()) { + genericsToAdd.addAll(Arrays.stream(resolvableType.getSuperType().getGenerics()).map(ResolvableType::toClass) + .collect(Collectors.toSet())); + } + } + + private static void addSuperTypesForClass(ResolvableType resolvableType, Set> supertypesToAdd, + Set> genericsToAdd) { + ResolvableType superType = resolvableType.getSuperType(); + if (!ResolvableType.NONE.equals(superType)) { + addGenericsForClass(genericsToAdd, superType); + supertypesToAdd.add(superType.toClass()); + addSuperTypesForClass(superType, supertypesToAdd, genericsToAdd); + } + } + @SuppressWarnings({ "rawtypes" }) private static Set> getClassesToAdd() { Set> classesToAdd = new HashSet<>(); diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessorTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessorTests.java new file mode 100644 index 00000000..e136701e --- /dev/null +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessorTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.gateway.config; + +import java.util.ArrayList; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.generate.GeneratedClass; +import org.springframework.aot.generate.GeneratedMethods; +import org.springframework.aot.generate.GenerationContext; +import org.springframework.aot.generate.MethodReference; +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.test.generate.TestGenerationContext; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerFilterFactory; +import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerResilience4JFilterFactory; +import org.springframework.javapoet.TypeSpec; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConfigurableHintsRegistrationProcessor}. + * + * @author Olga Maciaszek-Sharma + */ +class ConfigurableHintsRegistrationProcessorTests { + + private final ConfigurableHintsRegistrationProcessor processor = new ConfigurableHintsRegistrationProcessor(); + + private final TestGenerationContext generationContext = new TestGenerationContext(); + + private final BeanFactoryInitializationCode beanFactoryInitializationCode = new MockBeanFactoryInitializationCode( + generationContext); + + private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + + @Test + void shouldRegisterReflectionHintsForTypeAndSuperTypesAndGenerics() { + BeanDefinition beanDefinition = BeanDefinitionBuilder + .rootBeanDefinition(SpringCloudCircuitBreakerResilience4JFilterFactory.class).getBeanDefinition(); + beanFactory.registerBeanDefinition("test", beanDefinition); + + BeanFactoryInitializationAotContribution contribution = processor.processAheadOfTime(beanFactory); + assertThat(contribution).isNotNull(); + contribution.applyTo(generationContext, beanFactoryInitializationCode); + + ReflectionHints reflectionHints = generationContext.getRuntimeHints().reflection(); + assertThat(reflectionHints.getTypeHint(SpringCloudCircuitBreakerResilience4JFilterFactory.class)).isNotNull(); + assertThat(reflectionHints.getTypeHint(SpringCloudCircuitBreakerFilterFactory.class)).isNotNull(); + assertThat(reflectionHints.getTypeHint(SpringCloudCircuitBreakerFilterFactory.Config.class)).isNotNull(); + } + + @SuppressWarnings("NullableProblems") + static class MockBeanFactoryInitializationCode implements BeanFactoryInitializationCode { + + private static final Consumer emptyTypeCustomizer = type -> { + }; + + private final GeneratedClass generatedClass; + + MockBeanFactoryInitializationCode(GenerationContext generationContext) { + generatedClass = generationContext.getGeneratedClasses().addForFeature("Test", emptyTypeCustomizer); + } + + @Override + public GeneratedMethods getMethods() { + return generatedClass.getMethods(); + } + + @Override + public void addInitializer(MethodReference methodReference) { + new ArrayList<>(); + } + + } + +}