Skip to content

Commit

Permalink
Contribute introspection hints on registered beans
Browse files Browse the repository at this point in the history
Prior to this commit, reflection hints registered for beans was
selectively applied to only consider the methods that we'll actually
need reflection on at runtime. This would rely on an undocumented
behavior of GraalVM Native where calling `getDeclaredMethods` on a type
would only return known metadata at runtime, ignoring the ones that were
not registered during native compilation.

As of oracle/graal#5171, this behavior is now fixed in GraalVM and
aligns with the JVM behavior: all methods will be returned. This means
that if during native compilation, introspection was not registered for
the type a new `MissingReflectionMetadataException` will be raised.

As a follow up of spring-projects#29205, this commit contributes the "introspection on
declared method" reflection hint for all registered beans.

Closes spring-projectsgh-29246
  • Loading branch information
bclozel committed Mar 29, 2023
1 parent 7e905e3 commit b374824
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -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) {
}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -38,6 +40,7 @@
* @author Phillip Webb
* @author Sebastien Deleuze
* @author Stephane Nicoll
* @author Brian Clozel
* @since 6.0
* @see BeanRegistrationsAotProcessor
*/
Expand All @@ -46,9 +49,11 @@ class BeanRegistrationsAotContribution

private static final String BEAN_FACTORY_PARAMETER_NAME = "beanFactory";

private final Map<String, Registration> registrations;
private final Map<BeanRegistrationKey, Registration> registrations;

BeanRegistrationsAotContribution(Map<String, Registration> registrations) {

BeanRegistrationsAotContribution(
Map<BeanRegistrationKey, Registration> registrations) {
this.registrations = registrations;
}

Expand All @@ -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,
Expand All @@ -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());
Expand All @@ -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<BeanRegistrationKey, Registration> 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -31,6 +31,7 @@
* @author Phillip Webb
* @author Sebastien Deleuze
* @author Stephane Nicoll
* @author Brian Clozel
* @since 6.0
*/
class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProcessor {
Expand All @@ -40,15 +41,15 @@ class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProce
public BeanRegistrationsAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory =
new BeanDefinitionMethodGeneratorFactory(beanFactory);
Map<String, Registration> registrations = new LinkedHashMap<>();
Map<BeanRegistrationKey, Registration> 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)));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
Expand All @@ -55,6 +57,7 @@
* @author Phillip Webb
* @author Sebastien Deleuze
* @author Stephane Nicoll
* @author Brian Clozel
*/
class BeanRegistrationsAotContributionTests {

Expand Down Expand Up @@ -153,6 +156,21 @@ MethodReference generateBeanDefinitionMethod(
assertThat(actual.getMethods()).isNotNull();
}

@Test
void applyToRegisterReflectionHints() {
List<BeanRegistrationsCode> 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);
Expand Down Expand Up @@ -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)));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit b374824

Please sign in to comment.