Skip to content

Commit d03847c

Browse files
committed
Merge branch '3.3.x' into 3.4.x
Closes gh-44481
2 parents 849ca4c + e1f45c5 commit d03847c

File tree

3 files changed

+91
-8
lines changed

3 files changed

+91
-8
lines changed

spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java

+53-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 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.
@@ -19,11 +19,19 @@
1919
import java.lang.reflect.Method;
2020
import java.util.ArrayList;
2121
import java.util.Arrays;
22+
import java.util.Collection;
2223
import java.util.Collections;
2324
import java.util.List;
2425
import java.util.function.Consumer;
2526

27+
import org.springframework.aot.generate.GenerationContext;
28+
import org.springframework.aot.hint.ExecutableMode;
29+
import org.springframework.aot.hint.ReflectionHints;
2630
import org.springframework.beans.BeanUtils;
31+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
32+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
33+
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
34+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2735
import org.springframework.boot.ApplicationContextFactory;
2836
import org.springframework.boot.Banner;
2937
import org.springframework.boot.ConfigurableBootstrapContext;
@@ -158,20 +166,23 @@ private Method getMainMethod(MergedContextConfiguration mergedConfig, UseMainMet
158166
.orElse(null);
159167
Assert.state(springBootConfiguration != null || useMainMethod == UseMainMethod.WHEN_AVAILABLE,
160168
"Cannot use main method as no @SpringBootConfiguration-annotated class is available");
161-
Method mainMethod = (springBootConfiguration != null)
162-
? ReflectionUtils.findMethod(springBootConfiguration, "main", String[].class) : null;
169+
Method mainMethod = findMainMethod(springBootConfiguration);
170+
Assert.state(mainMethod != null || useMainMethod == UseMainMethod.WHEN_AVAILABLE,
171+
() -> "Main method not found on '%s'".formatted(springBootConfiguration.getName()));
172+
return mainMethod;
173+
}
174+
175+
private static Method findMainMethod(Class<?> type) {
176+
Method mainMethod = (type != null) ? ReflectionUtils.findMethod(type, "main", String[].class) : null;
163177
if (mainMethod == null && KotlinDetector.isKotlinPresent()) {
164178
try {
165-
Class<?> kotlinClass = ClassUtils.forName(springBootConfiguration.getName() + "Kt",
166-
springBootConfiguration.getClassLoader());
179+
Class<?> kotlinClass = ClassUtils.forName(type.getName() + "Kt", type.getClassLoader());
167180
mainMethod = ReflectionUtils.findMethod(kotlinClass, "main", String[].class);
168181
}
169182
catch (ClassNotFoundException ex) {
170183
// Ignore
171184
}
172185
}
173-
Assert.state(mainMethod != null || useMainMethod == UseMainMethod.WHEN_AVAILABLE,
174-
() -> "Main method not found on '%s'".formatted(springBootConfiguration.getName()));
175186
return mainMethod;
176187
}
177188

@@ -574,4 +585,39 @@ private ApplicationContext run(ThrowingSupplier<ConfigurableApplicationContext>
574585

575586
}
576587

588+
static class MainMethodBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
589+
590+
@Override
591+
public BeanFactoryInitializationAotContribution processAheadOfTime(
592+
ConfigurableListableBeanFactory beanFactory) {
593+
List<Method> mainMethods = new ArrayList<>();
594+
for (String beanName : beanFactory.getBeanDefinitionNames()) {
595+
Class<?> beanType = beanFactory.getType(beanName);
596+
Method mainMethod = findMainMethod(beanType);
597+
if (mainMethod != null) {
598+
mainMethods.add(mainMethod);
599+
}
600+
}
601+
return !mainMethods.isEmpty() ? new AotContribution(mainMethods) : null;
602+
}
603+
604+
static class AotContribution implements BeanFactoryInitializationAotContribution {
605+
606+
private final Collection<Method> mainMethods;
607+
608+
AotContribution(Collection<Method> mainMethods) {
609+
this.mainMethods = mainMethods;
610+
}
611+
612+
@Override
613+
public void applyTo(GenerationContext generationContext,
614+
BeanFactoryInitializationCode beanFactoryInitializationCode) {
615+
ReflectionHints reflectionHints = generationContext.getRuntimeHints().reflection();
616+
this.mainMethods.forEach((method) -> reflectionHints.registerMethod(method, ExecutableMode.INVOKE));
617+
}
618+
619+
}
620+
621+
}
622+
577623
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
2+
org.springframework.boot.test.context.SpringBootContextLoader.MainMethodBeanFactoryInitializationAotProcessor

spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 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.
@@ -25,10 +25,16 @@
2525
import org.junit.jupiter.api.Disabled;
2626
import org.junit.jupiter.api.Test;
2727

28+
import org.springframework.aot.hint.RuntimeHints;
29+
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
30+
import org.springframework.aot.test.generate.TestGenerationContext;
2831
import org.springframework.beans.factory.BeanCreationException;
32+
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
33+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2934
import org.springframework.boot.ApplicationContextFactory;
3035
import org.springframework.boot.SpringApplication;
3136
import org.springframework.boot.SpringBootConfiguration;
37+
import org.springframework.boot.test.context.SpringBootContextLoader.MainMethodBeanFactoryInitializationAotProcessor;
3238
import org.springframework.boot.test.context.SpringBootTest.UseMainMethod;
3339
import org.springframework.boot.test.util.TestPropertyValues;
3440
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext;
@@ -248,6 +254,35 @@ void whenUseMainMethodWithContextHierarchyThrowsException() {
248254
.withMessage("UseMainMethod.ALWAYS cannot be used with @ContextHierarchy tests");
249255
}
250256

257+
@Test
258+
void whenMainMethodPresentRegisterReflectionHints() {
259+
TestContext testContext = new ExposedTestContextManager(UseMainMethodWhenAvailableAndNoMainMethod.class)
260+
.getExposedTestContext();
261+
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) testContext
262+
.getApplicationContext()
263+
.getAutowireCapableBeanFactory();
264+
BeanFactoryInitializationAotContribution aotContribution = new MainMethodBeanFactoryInitializationAotProcessor()
265+
.processAheadOfTime(beanFactory);
266+
assertThat(aotContribution).isNull();
267+
}
268+
269+
@Test
270+
void whenMainMethodNotAvailableReturnsNoAotContribution() {
271+
TestContext testContext = new ExposedTestContextManager(UseMainMethodWhenAvailableAndMainMethod.class)
272+
.getExposedTestContext();
273+
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) testContext
274+
.getApplicationContext()
275+
.getAutowireCapableBeanFactory();
276+
BeanFactoryInitializationAotContribution aotContribution = new MainMethodBeanFactoryInitializationAotProcessor()
277+
.processAheadOfTime(beanFactory);
278+
assertThat(aotContribution).isNotNull();
279+
TestGenerationContext generationContext = new TestGenerationContext();
280+
aotContribution.applyTo(generationContext, null);
281+
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
282+
assertThat(RuntimeHintsPredicates.reflection().onMethod(ConfigWithMain.class, "main").invoke())
283+
.accepts(runtimeHints);
284+
}
285+
251286
@Test
252287
void whenSubclassProvidesCustomApplicationContextFactory() {
253288
TestContext testContext = new ExposedTestContextManager(CustomApplicationContextTest.class)

0 commit comments

Comments
 (0)