diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationKey.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationKey.java new file mode 100644 index 000000000000..bd99bebca962 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationKey.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2022 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.beans.factory.aot; + +/** + * Record class holding key information for beans registered in a bean factory. + * @param beanName the name of the registered bean + * @param beanClass the type of the registered bean + * @author Brian Clozel + * @since 6.0 + */ +record BeanRegistrationKey(String beanName, Class beanClass) { +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java index bda1d4713519..4e023dbd351f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -26,6 +26,8 @@ import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.generate.MethodReference; import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.javapoet.ClassName; import org.springframework.javapoet.CodeBlock; @@ -38,6 +40,7 @@ * @author Phillip Webb * @author Sebastien Deleuze * @author Stephane Nicoll + * @author Brian Clozel * @since 6.0 * @see BeanRegistrationsAotProcessor */ @@ -46,9 +49,11 @@ class BeanRegistrationsAotContribution private static final String BEAN_FACTORY_PARAMETER_NAME = "beanFactory"; - private final Map registrations; + private final Map registrations; - BeanRegistrationsAotContribution(Map registrations) { + + BeanRegistrationsAotContribution( + Map registrations) { this.registrations = registrations; } @@ -69,6 +74,7 @@ public void applyTo(GenerationContext generationContext, GeneratedMethod generatedAliasesMethod = codeGenerator.getMethods().add("registerAliases", this::generateRegisterAliasesMethod); beanFactoryInitializationCode.addInitializer(generatedAliasesMethod.toMethodReference()); + generateRegisterHints(generationContext.getRuntimeHints(), this.registrations); } private void generateRegisterBeanDefinitionsMethod(MethodSpec.Builder method, @@ -80,14 +86,14 @@ private void generateRegisterBeanDefinitionsMethod(MethodSpec.Builder method, method.addParameter(DefaultListableBeanFactory.class, BEAN_FACTORY_PARAMETER_NAME); CodeBlock.Builder code = CodeBlock.builder(); - this.registrations.forEach((beanName, registration) -> { + this.registrations.forEach((registeredBean, registration) -> { MethodReference beanDefinitionMethod = registration.methodGenerator .generateBeanDefinitionMethod(generationContext, beanRegistrationsCode); CodeBlock methodInvocation = beanDefinitionMethod.toInvokeCodeBlock( ArgumentCodeGenerator.none(), beanRegistrationsCode.getClassName()); code.addStatement("$L.registerBeanDefinition($S, $L)", - BEAN_FACTORY_PARAMETER_NAME, beanName, + BEAN_FACTORY_PARAMETER_NAME, registeredBean.beanName(), methodInvocation); }); method.addCode(code.build()); @@ -99,15 +105,20 @@ private void generateRegisterAliasesMethod(MethodSpec.Builder method) { method.addParameter(DefaultListableBeanFactory.class, BEAN_FACTORY_PARAMETER_NAME); CodeBlock.Builder code = CodeBlock.builder(); - this.registrations.forEach((beanName, registration) -> { + this.registrations.forEach((registeredBean, registration) -> { for (String alias : registration.aliases) { code.addStatement("$L.registerAlias($S, $S)", - BEAN_FACTORY_PARAMETER_NAME, beanName, alias); + BEAN_FACTORY_PARAMETER_NAME, registeredBean.beanName(), alias); } }); method.addCode(code.build()); } + private void generateRegisterHints(RuntimeHints runtimeHints, Map registrations) { + registrations.keySet().forEach(beanRegistrationKey -> runtimeHints.reflection() + .registerType(beanRegistrationKey.beanClass(), MemberCategory.INTROSPECT_DECLARED_METHODS)); + } + /** * Gather the necessary information to register a particular bean. * @param methodGenerator the {@link BeanDefinitionMethodGenerator} to use diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessor.java index 63bcf2fccce7..05df3e7a7c8b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -31,6 +31,7 @@ * @author Phillip Webb * @author Sebastien Deleuze * @author Stephane Nicoll + * @author Brian Clozel * @since 6.0 */ class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProcessor { @@ -40,15 +41,15 @@ class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProce public BeanRegistrationsAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory = new BeanDefinitionMethodGeneratorFactory(beanFactory); - Map registrations = new LinkedHashMap<>(); + Map registrations = new LinkedHashMap<>(); for (String beanName : beanFactory.getBeanDefinitionNames()) { RegisteredBean registeredBean = RegisteredBean.of(beanFactory, beanName); BeanDefinitionMethodGenerator beanDefinitionMethodGenerator = beanDefinitionMethodGeneratorFactory.getBeanDefinitionMethodGenerator(registeredBean); if (beanDefinitionMethodGenerator != null) { - registrations.put(beanName, new Registration(beanDefinitionMethodGenerator, - beanFactory.getAliases(beanName))); + registrations.put(new BeanRegistrationKey(beanName, registeredBean.getBeanClass()), + new Registration(beanDefinitionMethodGenerator, beanFactory.getAliases(beanName))); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java index 6ed07a5fd53c..93a2439e6f74 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -31,6 +31,8 @@ import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.generate.MethodReference; import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RegisteredBean; @@ -55,6 +57,7 @@ * @author Phillip Webb * @author Sebastien Deleuze * @author Stephane Nicoll + * @author Brian Clozel */ class BeanRegistrationsAotContributionTests { @@ -153,6 +156,21 @@ MethodReference generateBeanDefinitionMethod( assertThat(actual.getMethods()).isNotNull(); } + @Test + void applyToRegisterReflectionHints() { + List beanRegistrationsCodes = new ArrayList<>(); + RegisteredBean registeredBean = registerBean( + new RootBeanDefinition(TestBean.class)); + BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( + this.methodGeneratorFactory, registeredBean, null, + Collections.emptyList()); + BeanRegistrationsAotContribution contribution = createContribution(generator); + contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode); + assertThat(RuntimeHintsPredicates.reflection().onType(TestBean.class) + .withMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS)) + .accepts(this.generationContext.getRuntimeHints()); + } + private RegisteredBean registerBean(RootBeanDefinition rootBeanDefinition) { String beanName = "testBean"; this.beanFactory.registerBeanDefinition(beanName, rootBeanDefinition); @@ -186,7 +204,7 @@ private void compile( private BeanRegistrationsAotContribution createContribution( BeanDefinitionMethodGenerator methodGenerator,String... aliases) { - return new BeanRegistrationsAotContribution(Map.of("testBean", new Registration(methodGenerator, aliases))); + return new BeanRegistrationsAotContribution(Map.of(new BeanRegistrationKey("testBean", TestBean.class), new Registration(methodGenerator, aliases))); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessorTests.java index cb1e39f106db..2e627405847b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessorTests.java @@ -51,7 +51,7 @@ void processAheadOfTimeReturnsBeanRegistrationsAotContributionWithRegistrations( BeanRegistrationsAotContribution contribution = processor .processAheadOfTime(beanFactory); assertThat(contribution).extracting("registrations") - .asInstanceOf(InstanceOfAssertFactories.MAP).containsKeys("b1", "b2"); + .asInstanceOf(InstanceOfAssertFactories.MAP).hasSize(2); } @Test @@ -63,7 +63,7 @@ void processAheadOfTimeReturnsBeanRegistrationsAotContributionWithAliases() { BeanRegistrationsAotContribution contribution = processor .processAheadOfTime(beanFactory); assertThat(contribution).extracting("registrations").asInstanceOf(InstanceOfAssertFactories.MAP) - .hasEntrySatisfying("test", registration -> + .hasEntrySatisfying(new BeanRegistrationKey("test", TestBean.class), registration -> assertThat(registration).extracting("aliases").asInstanceOf(InstanceOfAssertFactories.ARRAY) .singleElement().isEqualTo("testAlias")); } diff --git a/spring-context/src/test/java/org/springframework/context/generator/ApplicationContextAotGeneratorRuntimeHintsTests.java b/spring-context/src/test/java/org/springframework/context/generator/ApplicationContextAotGeneratorRuntimeHintsTests.java index 3ec3f55d8ee5..a570748108fe 100644 --- a/spring-context/src/test/java/org/springframework/context/generator/ApplicationContextAotGeneratorRuntimeHintsTests.java +++ b/spring-context/src/test/java/org/springframework/context/generator/ApplicationContextAotGeneratorRuntimeHintsTests.java @@ -18,7 +18,6 @@ import java.util.function.BiConsumer; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.RuntimeHints; @@ -72,7 +71,6 @@ void generateApplicationContextWithInitDestroyMethods() { } @Test - @Disabled("until gh-29246 is re-applied") void generateApplicationContextWithMultipleInitDestroyMethods() { GenericApplicationContext context = new AnnotationConfigApplicationContext(); RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyComponent.class);