From a28ec3a0a87e640cc7c9e7e89cae0e232db38c81 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 15 Jul 2023 10:15:29 +0200 Subject: [PATCH 1/4] Make DefaultGenerationContext(,,) constructor protected See gh-30861 Closes gh-30895 --- .../aot/generate/DefaultGenerationContext.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/aot/generate/DefaultGenerationContext.java b/spring-core/src/main/java/org/springframework/aot/generate/DefaultGenerationContext.java index 67af5274360..ba63925c99a 100644 --- a/spring-core/src/main/java/org/springframework/aot/generate/DefaultGenerationContext.java +++ b/spring-core/src/main/java/org/springframework/aot/generate/DefaultGenerationContext.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. @@ -78,7 +78,7 @@ public class DefaultGenerationContext implements GenerationContext { * @param generatedFiles the generated files * @param runtimeHints the runtime hints */ - DefaultGenerationContext(GeneratedClasses generatedClasses, + protected DefaultGenerationContext(GeneratedClasses generatedClasses, GeneratedFiles generatedFiles, RuntimeHints runtimeHints) { Assert.notNull(generatedClasses, "'generatedClasses' must not be null"); From c354b1014d35fa75129308317c1c7026549a3dd2 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 15 Jul 2023 10:17:08 +0200 Subject: [PATCH 2/4] Make GeneratedClasses#withFeatureNamePrefix(String) public See gh-30861 Closes gh-30897 --- .../aot/generate/GeneratedClasses.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/aot/generate/GeneratedClasses.java b/spring-core/src/main/java/org/springframework/aot/generate/GeneratedClasses.java index aa768ff406f..62940a79252 100644 --- a/spring-core/src/main/java/org/springframework/aot/generate/GeneratedClasses.java +++ b/spring-core/src/main/java/org/springframework/aot/generate/GeneratedClasses.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. @@ -176,6 +176,19 @@ public class GeneratedClasses { return addForFeatureComponent(featureName, ClassName.get(targetComponent), type); } + /** + * Create a new {@link GeneratedClasses} instance using the specified feature + * name prefix to qualify generated class names for a dedicated round of code + * generation. + * @param featureNamePrefix the feature name prefix to use + * @return a new instance for the specified feature name prefix + * @since 6.0.12 + */ + public GeneratedClasses withFeatureNamePrefix(String featureNamePrefix) { + return new GeneratedClasses(this.classNameGenerator.withFeatureNamePrefix(featureNamePrefix), + this.classes, this.classesByOwner); + } + private GeneratedClass createAndAddGeneratedClass(String featureName, @Nullable ClassName targetComponent, Consumer type) { @@ -199,11 +212,6 @@ public class GeneratedClasses { } } - GeneratedClasses withFeatureNamePrefix(String name) { - return new GeneratedClasses(this.classNameGenerator.withFeatureNamePrefix(name), - this.classes, this.classesByOwner); - } - private record Owner(String featureNamePrefix, String featureName, @Nullable ClassName target) { } From 317c6fbec254ae15931349416024947cfb815053 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 15 Jul 2023 10:18:51 +0200 Subject: [PATCH 3/4] Introduce failOnError flag in TestContextAotGenerator This commit introduces a `failOnError` flag in TestContextAotGenerator. When set to `true`, any error encountered during AOT processing will result in an exception that fails the overall process. When set to `false` (the default), the previous behavior remains unchanged: a DEBUG or WARN message will be logged, and processing will continue. This feature is currently only used for internal testing. See gh-30861 Closes gh-30898 --- .../context/aot/TestContextAotGenerator.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java index 20c686e562a..b01dfce9999 100644 --- a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java +++ b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java @@ -85,6 +85,8 @@ public class TestContextAotGenerator { private final RuntimeHints runtimeHints; + private final boolean failOnError; + /** * Create a new {@link TestContextAotGenerator} that uses the supplied @@ -102,9 +104,23 @@ public class TestContextAotGenerator { * @param runtimeHints the {@code RuntimeHints} to use */ public TestContextAotGenerator(GeneratedFiles generatedFiles, RuntimeHints runtimeHints) { + this(generatedFiles, runtimeHints, false); + } + + /** + * Create a new {@link TestContextAotGenerator} that uses the supplied + * {@link GeneratedFiles}, {@link RuntimeHints}, and {@code failOnError} flag. + * @param generatedFiles the {@code GeneratedFiles} to use + * @param runtimeHints the {@code RuntimeHints} to use + * @param failOnError {@code true} if errors encountered during AOT processing + * should result in an exception that fails the overall process + * @since 6.0.12 + */ + TestContextAotGenerator(GeneratedFiles generatedFiles, RuntimeHints runtimeHints, boolean failOnError) { this.testRuntimeHintsRegistrars = AotServices.factories().load(TestRuntimeHintsRegistrar.class); this.generatedFiles = generatedFiles; this.runtimeHints = runtimeHints; + this.failOnError = failOnError; } @@ -210,6 +226,10 @@ public class TestContextAotGenerator { generationContext.writeGeneratedContent(); } catch (Exception ex) { + if (this.failOnError) { + throw new IllegalStateException("Failed to generate AOT artifacts for test classes " + + testClasses.stream().map(Class::getName).toList(), ex); + } if (logger.isDebugEnabled()) { logger.debug("Failed to generate AOT artifacts for test classes " + testClasses.stream().map(Class::getName).toList(), ex); From f5db8bd1e8b4b0712f069c3399645b02020c98d5 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 15 Jul 2023 10:51:56 +0200 Subject: [PATCH 4/4] Retain existing feature name as prefix in test AOT processing Prior to this commit, test AOT processing failed if a feature name for generated class names was used for more than one ApplicationContext. For example, when generating code for org.example.MessageService with a "Management" feature name, the "BeanDefinitions" class was named as follows (without a uniquely identifying TestContext###_ feature name prefix). org/example/MessageService__ManagementBeanDefinitions.java When another attempt was made to generate code for the MessageService using the same "Management" feature name, a FileAlreadyExistsException was thrown denoting that the class/file name was already in use. To avoid such naming collisions, this commit introduces a TestContextGenerationContext which provides a custom implementation of withName(String) that prepends an existing feature name (if present) to a new feature name, thereby treating any existing feature name as a prefix to a new, nested feature name. Consequently, code generation for the above example now results in unique class/file names like the following (which retain the uniquely identifying TestContext###_ prefixes). org/example/MessageService__TestContext002_ManagementBeanDefinitions.java org/example/MessageService__TestContext003_ManagementBeanDefinitions.java Closes gh-30861 --- .../context/aot/TestContextAotGenerator.java | 4 +- .../aot/TestContextGenerationContext.java | 85 +++++++++++++++++++ .../test/context/aot/AbstractAotTests.java | 10 ++- .../test/context/aot/AotIntegrationTests.java | 5 +- .../aot/TestContextAotGeneratorTests.java | 10 ++- .../BasicSpringJupiterSharedConfigTests.java | 5 +- .../basic/BasicSpringJupiterTests.java | 5 +- .../samples/basic/BasicTestConfiguration.java | 5 +- .../aot/samples/management/Managed.java | 35 ++++++++ .../management/ManagementConfiguration.java | 83 ++++++++++++++++++ .../management/ManagementMessageService.java | 36 ++++++++ 11 files changed, 272 insertions(+), 11 deletions(-) create mode 100644 spring-test/src/main/java/org/springframework/test/context/aot/TestContextGenerationContext.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/aot/samples/management/Managed.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/aot/samples/management/ManagementConfiguration.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/aot/samples/management/ManagementMessageService.java diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java index b01dfce9999..3629af2d49a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java +++ b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java @@ -324,8 +324,8 @@ public class TestContextAotGenerator { DefaultGenerationContext createGenerationContext(Class testClass) { ClassNameGenerator classNameGenerator = new ClassNameGenerator(ClassName.get(testClass)); - DefaultGenerationContext generationContext = - new DefaultGenerationContext(classNameGenerator, this.generatedFiles, this.runtimeHints); + TestContextGenerationContext generationContext = + new TestContextGenerationContext(classNameGenerator, this.generatedFiles, this.runtimeHints); return generationContext.withName(nextTestContextId()); } diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextGenerationContext.java b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextGenerationContext.java new file mode 100644 index 00000000000..2220386293a --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextGenerationContext.java @@ -0,0 +1,85 @@ +/* + * 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. + * 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.test.context.aot; + +import org.springframework.aot.generate.ClassNameGenerator; +import org.springframework.aot.generate.DefaultGenerationContext; +import org.springframework.aot.generate.GeneratedClasses; +import org.springframework.aot.generate.GeneratedFiles; +import org.springframework.aot.hint.RuntimeHints; + +/** + * Extension of {@link DefaultGenerationContext} with a custom implementation of + * {@link #withName(String)} that is specific to the Spring TestContext Framework. + * + * @author Sam Brannen + * @since 6.0.12 + */ +class TestContextGenerationContext extends DefaultGenerationContext { + + private final String featureName; + + + /** + * Create a new {@link TestContextGenerationContext} instance backed by the + * specified {@link ClassNameGenerator}, {@link GeneratedFiles}, and + * {@link RuntimeHints}. + * @param classNameGenerator the naming convention to use for generated class names + * @param generatedFiles the generated files + * @param runtimeHints the runtime hints + */ + TestContextGenerationContext(ClassNameGenerator classNameGenerator, GeneratedFiles generatedFiles, + RuntimeHints runtimeHints) { + super(classNameGenerator, generatedFiles, runtimeHints); + this.featureName = null; + } + + /** + * Create a new {@link TestContextGenerationContext} instance backed by the + * specified {@link GeneratedClasses}, {@link GeneratedFiles}, and + * {@link RuntimeHints}. + * @param generatedClasses the generated classes + * @param generatedFiles the generated files + * @param runtimeHints the runtime hints + */ + private TestContextGenerationContext(GeneratedClasses generatedClasses, GeneratedFiles generatedFiles, + RuntimeHints runtimeHints, String featureName) { + super(generatedClasses, generatedFiles, runtimeHints); + this.featureName = featureName; + } + + + /** + * Create a new {@link TestContextGenerationContext} instance using the specified + * feature name to qualify generated assets for a dedicated round of code generation. + *

If this {@code TestContextGenerationContext} has a configured feature + * name, the supplied feature name will be appended to the existing feature name + * in order to avoid naming collisions. + * @param featureName the feature name to use + * @return a specialized {@link TestContextGenerationContext} for the specified + * feature name + */ + @Override + public TestContextGenerationContext withName(String featureName) { + if (this.featureName != null) { + featureName = this.featureName + featureName; + } + GeneratedClasses generatedClasses = getGeneratedClasses().withFeatureNamePrefix(featureName); + return new TestContextGenerationContext(generatedClasses, getGeneratedFiles(), getRuntimeHints(), featureName); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/AbstractAotTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/AbstractAotTests.java index 20c67d1dfce..679451263c5 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/AbstractAotTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/AbstractAotTests.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. @@ -45,14 +45,22 @@ abstract class AbstractAotTests { "org/springframework/context/event/EventListenerMethodProcessor__TestContext002_BeanDefinitions.java", "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests__TestContext002_ApplicationContextInitializer.java", "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests__TestContext002_BeanFactoryRegistrations.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests__TestContext002_ManagementApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests__TestContext002_ManagementBeanFactoryRegistrations.java", "org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext002_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/management/ManagementConfiguration__TestContext002_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/management/ManagementMessageService__TestContext002_ManagementBeanDefinitions.java", // BasicSpringJupiterTests -- not generated b/c already generated for BasicSpringJupiterSharedConfigTests. // BasicSpringJupiterTests.NestedTests "org/springframework/context/event/DefaultEventListenerFactory__TestContext003_BeanDefinitions.java", "org/springframework/context/event/EventListenerMethodProcessor__TestContext003_BeanDefinitions.java", "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests_NestedTests__TestContext003_ApplicationContextInitializer.java", "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests_NestedTests__TestContext003_BeanFactoryRegistrations.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests_NestedTests__TestContext003_ManagementApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests_NestedTests__TestContext003_ManagementBeanFactoryRegistrations.java", "org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext003_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/management/ManagementConfiguration__TestContext003_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/management/ManagementMessageService__TestContext003_ManagementBeanDefinitions.java", // BasicSpringTestNGTests "org/springframework/context/event/DefaultEventListenerFactory__TestContext004_BeanDefinitions.java", "org/springframework/context/event/EventListenerMethodProcessor__TestContext004_BeanDefinitions.java", diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java index 89701d6969d..c7b749fc9a7 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/AotIntegrationTests.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. @@ -36,6 +36,7 @@ import org.opentest4j.MultipleFailuresError; import org.springframework.aot.AotDetector; import org.springframework.aot.generate.GeneratedFiles.Kind; import org.springframework.aot.generate.InMemoryGeneratedFiles; +import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.test.generate.CompilerFiles; import org.springframework.core.test.tools.CompileWithForkedClassLoader; import org.springframework.core.test.tools.TestCompiler; @@ -86,7 +87,7 @@ class AotIntegrationTests extends AbstractAotTests { // AOT BUILD-TIME: PROCESSING InMemoryGeneratedFiles generatedFiles = new InMemoryGeneratedFiles(); - TestContextAotGenerator generator = new TestContextAotGenerator(generatedFiles); + TestContextAotGenerator generator = new TestContextAotGenerator(generatedFiles, new RuntimeHints(), true); generator.processAheadOfTime(testClasses); List sourceFiles = generatedFiles.getGeneratedFiles(Kind.SOURCE).keySet().stream().toList(); diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java index 2ce0b3de509..d5e8e2ce7d7 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.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. @@ -366,14 +366,22 @@ class TestContextAotGeneratorTests extends AbstractAotTests { "org/springframework/context/event/EventListenerMethodProcessor__TestContext001_BeanDefinitions.java", "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests__TestContext001_ApplicationContextInitializer.java", "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests__TestContext001_BeanFactoryRegistrations.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests__TestContext001_ManagementApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests__TestContext001_ManagementBeanFactoryRegistrations.java", "org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext001_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/management/ManagementConfiguration__TestContext001_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/management/ManagementMessageService__TestContext001_ManagementBeanDefinitions.java", // BasicSpringJupiterTests -- not generated b/c already generated for BasicSpringJupiterSharedConfigTests. // BasicSpringJupiterTests.NestedTests "org/springframework/context/event/DefaultEventListenerFactory__TestContext002_BeanDefinitions.java", "org/springframework/context/event/EventListenerMethodProcessor__TestContext002_BeanDefinitions.java", "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests_NestedTests__TestContext002_ApplicationContextInitializer.java", "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests_NestedTests__TestContext002_BeanFactoryRegistrations.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests_NestedTests__TestContext002_ManagementApplicationContextInitializer.java", + "org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests_NestedTests__TestContext002_ManagementBeanFactoryRegistrations.java", "org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext002_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/management/ManagementConfiguration__TestContext002_BeanDefinitions.java", + "org/springframework/test/context/aot/samples/management/ManagementMessageService__TestContext002_ManagementBeanDefinitions.java", // BasicSpringTestNGTests "org/springframework/context/event/DefaultEventListenerFactory__TestContext003_BeanDefinitions.java", "org/springframework/context/event/EventListenerMethodProcessor__TestContext003_BeanDefinitions.java", diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests.java index 9c9320099d8..b2eed1edf43 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests.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. @@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.aot.samples.common.MessageService; +import org.springframework.test.context.aot.samples.management.ManagementConfiguration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.assertj.core.api.Assertions.assertThat; @@ -31,7 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Sam Brannen * @since 6.0 */ -@SpringJUnitConfig(BasicTestConfiguration.class) +@SpringJUnitConfig({BasicTestConfiguration.class, ManagementConfiguration.class}) @TestPropertySource(properties = "test.engine = jupiter") public class BasicSpringJupiterSharedConfigTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests.java index 505b6a77705..af6dfa72629 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests.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,7 @@ import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.aot.samples.basic.BasicSpringJupiterTests.DummyTestExecutionListener; import org.springframework.test.context.aot.samples.common.MessageService; +import org.springframework.test.context.aot.samples.management.ManagementConfiguration; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.support.AbstractTestExecutionListener; @@ -36,7 +37,7 @@ import static org.springframework.test.context.TestExecutionListeners.MergeMode. * @author Sam Brannen * @since 6.0 */ -@SpringJUnitConfig(BasicTestConfiguration.class) +@SpringJUnitConfig({BasicTestConfiguration.class, ManagementConfiguration.class}) @TestExecutionListeners(listeners = DummyTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS) @TestPropertySource(properties = "test.engine = jupiter") public class BasicSpringJupiterTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicTestConfiguration.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicTestConfiguration.java index 2bacfa98c6e..33f95821d6e 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicTestConfiguration.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicTestConfiguration.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. @@ -22,6 +22,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.test.context.aot.samples.common.DefaultMessageService; import org.springframework.test.context.aot.samples.common.MessageService; import org.springframework.test.context.aot.samples.common.SpanishMessageService; +import org.springframework.test.context.aot.samples.management.Managed; /** * @author Sam Brannen @@ -32,12 +33,14 @@ class BasicTestConfiguration { @Bean @Profile("default") + @Managed MessageService defaultMessageService() { return new DefaultMessageService(); } @Bean @Profile("spanish") + @Managed MessageService spanishMessageService() { return new SpanishMessageService(); } diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/management/Managed.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/management/Managed.java new file mode 100644 index 00000000000..7e501c2af22 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/management/Managed.java @@ -0,0 +1,35 @@ +/* + * 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. + * 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.test.context.aot.samples.management; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation for "managed" beans. + * + * @author Sam Brannen + * @since 6.0.12 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Managed { +} diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/management/ManagementConfiguration.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/management/ManagementConfiguration.java new file mode 100644 index 00000000000..7500bb6f44a --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/management/ManagementConfiguration.java @@ -0,0 +1,83 @@ +/* + * 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. + * 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.test.context.aot.samples.management; + +import java.lang.reflect.Executable; + +import org.springframework.aot.generate.GenerationContext; +import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; +import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; +import org.springframework.beans.factory.aot.BeanRegistrationCode; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.aot.ApplicationContextAotGenerator; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.annotation.AnnotatedElementUtils; + +/** + * Configuration class that mimics Spring Boot's AOT support for child management + * contexts in + * {@code org.springframework.boot.actuate.autoconfigure.web.server.ChildManagementContextInitializer}. + * + *

See gh-30861. + * + * @author Sam Brannen + * @since 6.0.12 + */ +@Configuration +public class ManagementConfiguration { + + @Bean + static BeanRegistrationAotProcessor beanRegistrationAotProcessor() { + return registeredBean -> { + Executable factoryMethod = registeredBean.resolveConstructorOrFactoryMethod(); + // Make AOT contribution for @Managed @Bean methods. + if (AnnotatedElementUtils.hasAnnotation(factoryMethod, Managed.class)) { + return new AotContribution(createManagementContext()); + } + return null; + }; + } + + private static GenericApplicationContext createManagementContext() { + GenericApplicationContext managementContext = new GenericApplicationContext(); + managementContext.registerBean(ManagementMessageService.class); + return managementContext; + } + + + /** + * Mimics Spring Boot's AOT support for child management contexts in + * {@code org.springframework.boot.actuate.autoconfigure.web.server.ChildManagementContextInitializer.AotContribution}. + */ + private static class AotContribution implements BeanRegistrationAotContribution { + + private final GenericApplicationContext managementContext; + + AotContribution(GenericApplicationContext managementContext) { + this.managementContext = managementContext; + } + + @Override + public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) { + GenerationContext managementGenerationContext = generationContext.withName("Management"); + new ApplicationContextAotGenerator().processAheadOfTime(this.managementContext, managementGenerationContext); + } + + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/management/ManagementMessageService.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/management/ManagementMessageService.java new file mode 100644 index 00000000000..606eba7dde2 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/management/ManagementMessageService.java @@ -0,0 +1,36 @@ +/* + * 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. + * 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.test.context.aot.samples.management; + +import org.springframework.test.context.aot.samples.common.MessageService; + +/** + * {@link MessageService} implemented in a different package. + * + *

See gh-30861. + * + * @author Sam Brannen + * @since 6.0.12 + */ +class ManagementMessageService implements MessageService { + + @Override + public String generateMessage() { + return "Hello, Management!"; + } + +}