Skip to content

Commit b374824

Browse files
committed
Contribute introspection hints on registered beans
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 #29205, this commit contributes the "introspection on declared method" reflection hint for all registered beans. Closes gh-29246
1 parent 7e905e3 commit b374824

File tree

6 files changed

+72
-17
lines changed

6 files changed

+72
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.beans.factory.aot;
18+
19+
/**
20+
* Record class holding key information for beans registered in a bean factory.
21+
* @param beanName the name of the registered bean
22+
* @param beanClass the type of the registered bean
23+
* @author Brian Clozel
24+
* @since 6.0
25+
*/
26+
record BeanRegistrationKey(String beanName, Class<?> beanClass) {
27+
}

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,8 @@
2626
import org.springframework.aot.generate.GenerationContext;
2727
import org.springframework.aot.generate.MethodReference;
2828
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
29+
import org.springframework.aot.hint.MemberCategory;
30+
import org.springframework.aot.hint.RuntimeHints;
2931
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
3032
import org.springframework.javapoet.ClassName;
3133
import org.springframework.javapoet.CodeBlock;
@@ -38,6 +40,7 @@
3840
* @author Phillip Webb
3941
* @author Sebastien Deleuze
4042
* @author Stephane Nicoll
43+
* @author Brian Clozel
4144
* @since 6.0
4245
* @see BeanRegistrationsAotProcessor
4346
*/
@@ -46,9 +49,11 @@ class BeanRegistrationsAotContribution
4649

4750
private static final String BEAN_FACTORY_PARAMETER_NAME = "beanFactory";
4851

49-
private final Map<String, Registration> registrations;
52+
private final Map<BeanRegistrationKey, Registration> registrations;
5053

51-
BeanRegistrationsAotContribution(Map<String, Registration> registrations) {
54+
55+
BeanRegistrationsAotContribution(
56+
Map<BeanRegistrationKey, Registration> registrations) {
5257
this.registrations = registrations;
5358
}
5459

@@ -69,6 +74,7 @@ public void applyTo(GenerationContext generationContext,
6974
GeneratedMethod generatedAliasesMethod = codeGenerator.getMethods().add("registerAliases",
7075
this::generateRegisterAliasesMethod);
7176
beanFactoryInitializationCode.addInitializer(generatedAliasesMethod.toMethodReference());
77+
generateRegisterHints(generationContext.getRuntimeHints(), this.registrations);
7278
}
7379

7480
private void generateRegisterBeanDefinitionsMethod(MethodSpec.Builder method,
@@ -80,14 +86,14 @@ private void generateRegisterBeanDefinitionsMethod(MethodSpec.Builder method,
8086
method.addParameter(DefaultListableBeanFactory.class,
8187
BEAN_FACTORY_PARAMETER_NAME);
8288
CodeBlock.Builder code = CodeBlock.builder();
83-
this.registrations.forEach((beanName, registration) -> {
89+
this.registrations.forEach((registeredBean, registration) -> {
8490
MethodReference beanDefinitionMethod = registration.methodGenerator
8591
.generateBeanDefinitionMethod(generationContext,
8692
beanRegistrationsCode);
8793
CodeBlock methodInvocation = beanDefinitionMethod.toInvokeCodeBlock(
8894
ArgumentCodeGenerator.none(), beanRegistrationsCode.getClassName());
8995
code.addStatement("$L.registerBeanDefinition($S, $L)",
90-
BEAN_FACTORY_PARAMETER_NAME, beanName,
96+
BEAN_FACTORY_PARAMETER_NAME, registeredBean.beanName(),
9197
methodInvocation);
9298
});
9399
method.addCode(code.build());
@@ -99,15 +105,20 @@ private void generateRegisterAliasesMethod(MethodSpec.Builder method) {
99105
method.addParameter(DefaultListableBeanFactory.class,
100106
BEAN_FACTORY_PARAMETER_NAME);
101107
CodeBlock.Builder code = CodeBlock.builder();
102-
this.registrations.forEach((beanName, registration) -> {
108+
this.registrations.forEach((registeredBean, registration) -> {
103109
for (String alias : registration.aliases) {
104110
code.addStatement("$L.registerAlias($S, $S)",
105-
BEAN_FACTORY_PARAMETER_NAME, beanName, alias);
111+
BEAN_FACTORY_PARAMETER_NAME, registeredBean.beanName(), alias);
106112
}
107113
});
108114
method.addCode(code.build());
109115
}
110116

117+
private void generateRegisterHints(RuntimeHints runtimeHints, Map<BeanRegistrationKey, Registration> registrations) {
118+
registrations.keySet().forEach(beanRegistrationKey -> runtimeHints.reflection()
119+
.registerType(beanRegistrationKey.beanClass(), MemberCategory.INTROSPECT_DECLARED_METHODS));
120+
}
121+
111122
/**
112123
* Gather the necessary information to register a particular bean.
113124
* @param methodGenerator the {@link BeanDefinitionMethodGenerator} to use

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessor.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
* @author Phillip Webb
3232
* @author Sebastien Deleuze
3333
* @author Stephane Nicoll
34+
* @author Brian Clozel
3435
* @since 6.0
3536
*/
3637
class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProcessor {
@@ -40,15 +41,15 @@ class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProce
4041
public BeanRegistrationsAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) {
4142
BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory =
4243
new BeanDefinitionMethodGeneratorFactory(beanFactory);
43-
Map<String, Registration> registrations = new LinkedHashMap<>();
44+
Map<BeanRegistrationKey, Registration> registrations = new LinkedHashMap<>();
4445

4546
for (String beanName : beanFactory.getBeanDefinitionNames()) {
4647
RegisteredBean registeredBean = RegisteredBean.of(beanFactory, beanName);
4748
BeanDefinitionMethodGenerator beanDefinitionMethodGenerator =
4849
beanDefinitionMethodGeneratorFactory.getBeanDefinitionMethodGenerator(registeredBean);
4950
if (beanDefinitionMethodGenerator != null) {
50-
registrations.put(beanName, new Registration(beanDefinitionMethodGenerator,
51-
beanFactory.getAliases(beanName)));
51+
registrations.put(new BeanRegistrationKey(beanName, registeredBean.getBeanClass()),
52+
new Registration(beanDefinitionMethodGenerator, beanFactory.getAliases(beanName)));
5253
}
5354
}
5455

Diff for: spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,8 @@
3131
import org.springframework.aot.generate.GenerationContext;
3232
import org.springframework.aot.generate.MethodReference;
3333
import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator;
34+
import org.springframework.aot.hint.MemberCategory;
35+
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
3436
import org.springframework.aot.test.generate.TestGenerationContext;
3537
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
3638
import org.springframework.beans.factory.support.RegisteredBean;
@@ -55,6 +57,7 @@
5557
* @author Phillip Webb
5658
* @author Sebastien Deleuze
5759
* @author Stephane Nicoll
60+
* @author Brian Clozel
5861
*/
5962
class BeanRegistrationsAotContributionTests {
6063

@@ -153,6 +156,21 @@ MethodReference generateBeanDefinitionMethod(
153156
assertThat(actual.getMethods()).isNotNull();
154157
}
155158

159+
@Test
160+
void applyToRegisterReflectionHints() {
161+
List<BeanRegistrationsCode> beanRegistrationsCodes = new ArrayList<>();
162+
RegisteredBean registeredBean = registerBean(
163+
new RootBeanDefinition(TestBean.class));
164+
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
165+
this.methodGeneratorFactory, registeredBean, null,
166+
Collections.emptyList());
167+
BeanRegistrationsAotContribution contribution = createContribution(generator);
168+
contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode);
169+
assertThat(RuntimeHintsPredicates.reflection().onType(TestBean.class)
170+
.withMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS))
171+
.accepts(this.generationContext.getRuntimeHints());
172+
}
173+
156174
private RegisteredBean registerBean(RootBeanDefinition rootBeanDefinition) {
157175
String beanName = "testBean";
158176
this.beanFactory.registerBeanDefinition(beanName, rootBeanDefinition);
@@ -186,7 +204,7 @@ private void compile(
186204

187205
private BeanRegistrationsAotContribution createContribution(
188206
BeanDefinitionMethodGenerator methodGenerator,String... aliases) {
189-
return new BeanRegistrationsAotContribution(Map.of("testBean", new Registration(methodGenerator, aliases)));
207+
return new BeanRegistrationsAotContribution(Map.of(new BeanRegistrationKey("testBean", TestBean.class), new Registration(methodGenerator, aliases)));
190208
}
191209

192210
}

Diff for: spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotProcessorTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ void processAheadOfTimeReturnsBeanRegistrationsAotContributionWithRegistrations(
5151
BeanRegistrationsAotContribution contribution = processor
5252
.processAheadOfTime(beanFactory);
5353
assertThat(contribution).extracting("registrations")
54-
.asInstanceOf(InstanceOfAssertFactories.MAP).containsKeys("b1", "b2");
54+
.asInstanceOf(InstanceOfAssertFactories.MAP).hasSize(2);
5555
}
5656

5757
@Test
@@ -63,7 +63,7 @@ void processAheadOfTimeReturnsBeanRegistrationsAotContributionWithAliases() {
6363
BeanRegistrationsAotContribution contribution = processor
6464
.processAheadOfTime(beanFactory);
6565
assertThat(contribution).extracting("registrations").asInstanceOf(InstanceOfAssertFactories.MAP)
66-
.hasEntrySatisfying("test", registration ->
66+
.hasEntrySatisfying(new BeanRegistrationKey("test", TestBean.class), registration ->
6767
assertThat(registration).extracting("aliases").asInstanceOf(InstanceOfAssertFactories.ARRAY)
6868
.singleElement().isEqualTo("testAlias"));
6969
}

Diff for: spring-context/src/test/java/org/springframework/context/generator/ApplicationContextAotGeneratorRuntimeHintsTests.java

-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.util.function.BiConsumer;
2020

21-
import org.junit.jupiter.api.Disabled;
2221
import org.junit.jupiter.api.Test;
2322

2423
import org.springframework.aot.hint.RuntimeHints;
@@ -72,7 +71,6 @@ void generateApplicationContextWithInitDestroyMethods() {
7271
}
7372

7473
@Test
75-
@Disabled("until gh-29246 is re-applied")
7674
void generateApplicationContextWithMultipleInitDestroyMethods() {
7775
GenericApplicationContext context = new AnnotationConfigApplicationContext();
7876
RootBeanDefinition beanDefinition = new RootBeanDefinition(InitDestroyComponent.class);

0 commit comments

Comments
 (0)