diff --git a/spring-context/src/main/java/org/springframework/context/aot/AbstractAotProcessor.java b/spring-context/src/main/java/org/springframework/context/aot/AbstractAotProcessor.java
new file mode 100644
index 0000000000..de9bffc103
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/context/aot/AbstractAotProcessor.java
@@ -0,0 +1,148 @@
+/*
+ * 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.context.aot;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import org.springframework.aot.generate.FileSystemGeneratedFiles;
+import org.springframework.aot.generate.GeneratedFiles.Kind;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.nativex.FileNativeConfigurationWriter;
+import org.springframework.util.FileSystemUtils;
+
+/**
+ * Abstract base class for filesystem-based ahead-of-time (AOT) processing.
+ *
+ *
Concrete implementations are typically used to kick off optimization of an
+ * application or test suite in a build tool.
+ *
+ * @author Stephane Nicoll
+ * @author Andy Wilkinson
+ * @author Phillip Webb
+ * @author Sam Brannen
+ * @since 6.0
+ * @see FileSystemGeneratedFiles
+ * @see FileNativeConfigurationWriter
+ * @see org.springframework.context.aot.ContextAotProcessor
+ * @see org.springframework.test.context.aot.TestAotProcessor
+ */
+public abstract class AbstractAotProcessor {
+
+ private final Path sourceOutput;
+
+ private final Path resourceOutput;
+
+ private final Path classOutput;
+
+ private final String groupId;
+
+ private final String artifactId;
+
+
+ /**
+ * Create a new processor instance.
+ * @param sourceOutput the location of generated sources
+ * @param resourceOutput the location of generated resources
+ * @param classOutput the location of generated classes
+ * @param groupId the group ID of the application, used to locate
+ * {@code native-image.properties}
+ * @param artifactId the artifact ID of the application, used to locate
+ * {@code native-image.properties}
+ */
+ protected AbstractAotProcessor(Path sourceOutput, Path resourceOutput,
+ Path classOutput, String groupId, String artifactId) {
+
+ this.sourceOutput = sourceOutput;
+ this.resourceOutput = resourceOutput;
+ this.classOutput = classOutput;
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ }
+
+ /**
+ * Get the output directory for generated sources.
+ */
+ protected Path getSourceOutput() {
+ return this.sourceOutput;
+ }
+
+ /**
+ * Get the output directory for generated resources.
+ */
+ protected Path getResourceOutput() {
+ return this.resourceOutput;
+ }
+
+ /**
+ * Get the output directory for generated classes.
+ */
+ protected Path getClassOutput() {
+ return this.classOutput;
+ }
+
+ /**
+ * Get the group ID of the application.
+ */
+ protected String getGroupId() {
+ return this.groupId;
+ }
+
+ /**
+ * Get the artifact ID of the application.
+ */
+ protected String getArtifactId() {
+ return this.artifactId;
+ }
+
+ /**
+ * Delete the source, resource, and class output directories.
+ */
+ protected void deleteExistingOutput() {
+ deleteExistingOutput(getSourceOutput(), getResourceOutput(), getClassOutput());
+ }
+
+ private void deleteExistingOutput(Path... paths) {
+ for (Path path : paths) {
+ try {
+ FileSystemUtils.deleteRecursively(path);
+ }
+ catch (IOException ex) {
+ throw new RuntimeException("Failed to delete existing output in '" + path + "'");
+ }
+ }
+ }
+
+ protected FileSystemGeneratedFiles createFileSystemGeneratedFiles() {
+ return new FileSystemGeneratedFiles(this::getRoot);
+ }
+
+ private Path getRoot(Kind kind) {
+ return switch (kind) {
+ case SOURCE -> getSourceOutput();
+ case RESOURCE -> getResourceOutput();
+ case CLASS -> getClassOutput();
+ };
+ }
+
+ protected void writeHints(RuntimeHints hints) {
+ FileNativeConfigurationWriter writer =
+ new FileNativeConfigurationWriter(getResourceOutput(), getGroupId(), getArtifactId());
+ writer.write(hints);
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/context/aot/AotProcessor.java b/spring-context/src/main/java/org/springframework/context/aot/ContextAotProcessor.java
similarity index 75%
rename from spring-context/src/main/java/org/springframework/context/aot/AotProcessor.java
rename to spring-context/src/main/java/org/springframework/context/aot/ContextAotProcessor.java
index 26d7e68d0f..c645a47f54 100644
--- a/spring-context/src/main/java/org/springframework/context/aot/AotProcessor.java
+++ b/spring-context/src/main/java/org/springframework/context/aot/ContextAotProcessor.java
@@ -26,40 +26,29 @@ import java.util.List;
import org.springframework.aot.generate.ClassNameGenerator;
import org.springframework.aot.generate.DefaultGenerationContext;
import org.springframework.aot.generate.FileSystemGeneratedFiles;
-import org.springframework.aot.generate.GeneratedFiles.Kind;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.ReflectionHints;
-import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
-import org.springframework.aot.nativex.FileNativeConfigurationWriter;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.javapoet.ClassName;
import org.springframework.util.CollectionUtils;
-import org.springframework.util.FileSystemUtils;
/**
- * Filesystem-based ahead-of-time processing base implementation. Typically
- * used to kick off the optimizations of an application in a build tool.
+ * Filesystem-based ahead-of-time (AOT) processing base implementation.
+ *
+ *
Concrete implementations are typically used to kick off optimization of an
+ * application in a build tool.
*
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Phillip Webb
* @since 6.0
+ * @see org.springframework.test.context.aot.TestAotProcessor
*/
-public abstract class AotProcessor {
+public abstract class ContextAotProcessor extends AbstractAotProcessor {
private final Class> application;
- private final Path sourceOutput;
-
- private final Path resourceOutput;
-
- private final Path classOutput;
-
- private final String groupId;
-
- private final String artifactId;
-
/**
* Create a new processor instance.
* @param application the application entry point
@@ -71,25 +60,13 @@ public abstract class AotProcessor {
* @param artifactId the artifact ID of the application, used to locate
* {@code native-image.properties}
*/
- protected AotProcessor(Class> application, Path sourceOutput, Path resourceOutput,
+ protected ContextAotProcessor(Class> application, Path sourceOutput, Path resourceOutput,
Path classOutput, String groupId, String artifactId) {
+ super(sourceOutput, resourceOutput, classOutput, groupId, artifactId);
this.application = application;
- this.sourceOutput = sourceOutput;
- this.resourceOutput = resourceOutput;
- this.classOutput = classOutput;
- this.groupId = groupId;
- this.artifactId = artifactId;
}
- /**
- * Prepare the {@link GenericApplicationContext} for the specified
- * application to be used against an {@link ApplicationContextAotGenerator}.
- * @return a non-refreshed {@link GenericApplicationContext}
- */
- protected abstract GenericApplicationContext prepareApplicationContext(Class> application);
-
-
/**
* Invoke the processing by clearing output directories first, followed by
* {@link #performAotProcessing(GenericApplicationContext)}.
@@ -103,11 +80,11 @@ public abstract class AotProcessor {
}
/**
- * Delete the source, resource, and class output directories.
+ * Prepare the {@link GenericApplicationContext} for the specified
+ * application to be used against an {@link ApplicationContextAotGenerator}.
+ * @return a non-refreshed {@link GenericApplicationContext}
*/
- protected void deleteExistingOutput() {
- deleteExistingOutput(this.sourceOutput, this.resourceOutput, this.classOutput);
- }
+ protected abstract GenericApplicationContext prepareApplicationContext(Class> application);
/**
* Perform ahead-of-time processing of the specified context.
@@ -117,7 +94,7 @@ public abstract class AotProcessor {
* @param applicationContext the context to process
*/
protected ClassName performAotProcessing(GenericApplicationContext applicationContext) {
- FileSystemGeneratedFiles generatedFiles = new FileSystemGeneratedFiles(this::getRoot);
+ FileSystemGeneratedFiles generatedFiles = createFileSystemGeneratedFiles();
DefaultGenerationContext generationContext = new DefaultGenerationContext(
createClassNameGenerator(), generatedFiles);
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
@@ -167,12 +144,6 @@ public abstract class AotProcessor {
.withConstructor(Collections.emptyList(), ExecutableMode.INVOKE));
}
- private void writeHints(RuntimeHints hints) {
- FileNativeConfigurationWriter writer =
- new FileNativeConfigurationWriter(this.resourceOutput, this.groupId, this.artifactId);
- writer.write(hints);
- }
-
private void writeNativeImageProperties(List args) {
if (CollectionUtils.isEmpty(args)) {
return;
@@ -180,8 +151,8 @@ public abstract class AotProcessor {
StringBuilder sb = new StringBuilder();
sb.append("Args = ");
sb.append(String.join(String.format(" \\%n"), args));
- Path file = this.resourceOutput
- .resolve("META-INF/native-image/" + this.groupId + "/" + this.artifactId + "/native-image.properties");
+ Path file = getResourceOutput()
+ .resolve("META-INF/native-image/" + getGroupId() + "/" + getArtifactId() + "/native-image.properties");
try {
if (!Files.exists(file)) {
Files.createDirectories(file.getParent());
@@ -190,27 +161,8 @@ public abstract class AotProcessor {
Files.writeString(file, sb.toString());
}
catch (IOException ex) {
- throw new IllegalStateException("Failed to write native-image properties", ex);
+ throw new IllegalStateException("Failed to write native-image.properties", ex);
}
}
- private void deleteExistingOutput(Path... paths) {
- for (Path path : paths) {
- try {
- FileSystemUtils.deleteRecursively(path);
- }
- catch (IOException ex) {
- throw new RuntimeException("Failed to delete existing output in '" + path + "'");
- }
- }
- }
-
- private Path getRoot(Kind kind) {
- return switch (kind) {
- case SOURCE -> this.sourceOutput;
- case RESOURCE -> this.resourceOutput;
- case CLASS -> this.classOutput;
- };
- }
-
}
diff --git a/spring-context/src/test/java/org/springframework/context/aot/AotProcessorTests.java b/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java
similarity index 81%
rename from spring-context/src/test/java/org/springframework/context/aot/AotProcessorTests.java
rename to spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java
index 3152cdc905..1b306574fe 100644
--- a/spring-context/src/test/java/org/springframework/context/aot/AotProcessorTests.java
+++ b/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java
@@ -35,20 +35,20 @@ import org.springframework.javapoet.ClassName;
import static org.assertj.core.api.Assertions.assertThat;
/**
- * Tests for {@link AotProcessor}.
+ * Tests for {@link ContextAotProcessor}.
*
* @author Stephane Nicoll
*/
-class AotProcessorTests {
+class ContextAotProcessorTests {
@Test
void processGeneratesAssets(@TempDir Path directory) {
GenericApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(SampleApplication.class);
- AotProcessor processor = new TestAotProcessor(SampleApplication.class, directory);
+ ContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, directory);
ClassName className = processor.process();
assertThat(className).isEqualTo(ClassName.get(SampleApplication.class.getPackageName(),
- "AotProcessorTests_SampleApplication__ApplicationContextInitializer"));
+ "ContextAotProcessorTests_SampleApplication__ApplicationContextInitializer"));
assertThat(directory).satisfies(hasGeneratedAssetsForSampleApplication());
context.close();
}
@@ -61,7 +61,7 @@ class AotProcessorTests {
Path existingSourceOutput = createExisting(sourceOutput);
Path existingResourceOutput = createExisting(resourceOutput);
Path existingClassOutput = createExisting(classOutput);
- AotProcessor processor = new TestAotProcessor(SampleApplication.class,
+ ContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class,
sourceOutput, resourceOutput, classOutput);
processor.process();
assertThat(existingSourceOutput).doesNotExist();
@@ -73,7 +73,7 @@ class AotProcessorTests {
void processWithEmptyNativeImageArgumentsDoesNotCreateNativeImageProperties(@TempDir Path directory) {
GenericApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(SampleApplication.class);
- AotProcessor processor = new TestAotProcessor(SampleApplication.class, directory) {
+ ContextAotProcessor processor = new DemoContextAotProcessor(SampleApplication.class, directory) {
@Override
protected List getDefaultNativeImageArguments(String application) {
return Collections.emptyList();
@@ -95,19 +95,19 @@ class AotProcessorTests {
private Consumer hasGeneratedAssetsForSampleApplication() {
return directory -> {
assertThat(directory.resolve(
- "source/org/springframework/context/aot/AotProcessorTests_SampleApplication__ApplicationContextInitializer.java"))
+ "source/org/springframework/context/aot/ContextAotProcessorTests_SampleApplication__ApplicationContextInitializer.java"))
.exists().isRegularFile();
- assertThat(directory.resolve("source/org/springframework/context/aot/AotProcessorTests__BeanDefinitions.java"))
+ assertThat(directory.resolve("source/org/springframework/context/aot/ContextAotProcessorTests__BeanDefinitions.java"))
.exists().isRegularFile();
assertThat(directory.resolve(
- "source/org/springframework/context/aot/AotProcessorTests_SampleApplication__BeanFactoryRegistrations.java"))
+ "source/org/springframework/context/aot/ContextAotProcessorTests_SampleApplication__BeanFactoryRegistrations.java"))
.exists().isRegularFile();
assertThat(directory.resolve("resource/META-INF/native-image/com.example/example/reflect-config.json"))
.exists().isRegularFile();
Path nativeImagePropertiesFile = directory
.resolve("resource/META-INF/native-image/com.example/example/native-image.properties");
assertThat(nativeImagePropertiesFile).exists().isRegularFile().hasContent("""
- Args = -H:Class=org.springframework.context.aot.AotProcessorTests$SampleApplication \\
+ Args = -H:Class=org.springframework.context.aot.ContextAotProcessorTests$SampleApplication \\
--report-unsupported-elements-at-runtime \\
--no-fallback \\
--install-exit-handlers
@@ -116,14 +116,14 @@ class AotProcessorTests {
}
- private static class TestAotProcessor extends AotProcessor {
+ private static class DemoContextAotProcessor extends ContextAotProcessor {
- public TestAotProcessor(Class> application,
+ DemoContextAotProcessor(Class> application,
Path sourceOutput, Path resourceOutput, Path classOutput) {
super(application, sourceOutput, resourceOutput, classOutput, "com.example", "example");
}
- public TestAotProcessor(Class> application, Path rootPath) {
+ DemoContextAotProcessor(Class> application, Path rootPath) {
super(application, rootPath.resolve("source"), rootPath.resolve("resource"),
rootPath.resolve("class"), "com.example", "example");
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/TestAotProcessor.java b/spring-test/src/main/java/org/springframework/test/context/aot/TestAotProcessor.java
index 188806448a..19cd25e29b 100644
--- a/spring-test/src/main/java/org/springframework/test/context/aot/TestAotProcessor.java
+++ b/spring-test/src/main/java/org/springframework/test/context/aot/TestAotProcessor.java
@@ -16,52 +16,29 @@
package org.springframework.test.context.aot;
-import java.io.File;
-import java.io.IOException;
-import java.io.UncheckedIOException;
import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
import java.util.Set;
import java.util.stream.Stream;
-import org.springframework.aot.generate.FileSystemGeneratedFiles;
import org.springframework.aot.generate.GeneratedFiles;
-import org.springframework.aot.generate.GeneratedFiles.Kind;
-import org.springframework.aot.hint.RuntimeHints;
-import org.springframework.aot.nativex.FileNativeConfigurationWriter;
-import org.springframework.util.Assert;
-import org.springframework.util.FileSystemUtils;
+import org.springframework.context.aot.AbstractAotProcessor;
/**
* Filesystem-based ahead-of-time (AOT) processing base implementation that scans
* the provided classpath roots for Spring integration test classes and then
* generates AOT artifacts for those test classes in the configured output directories.
*
- * Typically used in a build tool.
+ *
Concrete implementations are typically used to kick off optimization of a
+ * test suite in a build tool.
*
* @author Sam Brannen
- * @author Stephane Nicoll
- * @author Andy Wilkinson
- * @author Phillip Webb
* @since 6.0
* @see TestContextAotGenerator
- * @see FileNativeConfigurationWriter
- * @see org.springframework.context.aot.AotProcessor
+ * @see org.springframework.context.aot.ContextAotProcessor
*/
-public class TestAotProcessor {
+public abstract class TestAotProcessor extends AbstractAotProcessor {
- private final Path[] classpathRoots;
-
- private final Path sourceOutput;
-
- private final Path resourceOutput;
-
- private final Path classOutput;
-
- private final String groupId;
-
- private final String artifactId;
+ private final Set classpathRoots;
/**
@@ -76,15 +53,11 @@ public class TestAotProcessor {
* @param artifactId the artifact ID of the application, used to locate
* {@code native-image.properties}
*/
- public TestAotProcessor(Path[] classpathRoots, Path sourceOutput, Path resourceOutput, Path classOutput,
+ public TestAotProcessor(Set classpathRoots, Path sourceOutput, Path resourceOutput, Path classOutput,
String groupId, String artifactId) {
+ super(sourceOutput, resourceOutput, classOutput, groupId, artifactId);
this.classpathRoots = classpathRoots;
- this.sourceOutput = sourceOutput;
- this.resourceOutput = resourceOutput;
- this.classOutput = classOutput;
- this.groupId = groupId;
- this.artifactId = artifactId;
}
@@ -98,24 +71,6 @@ public class TestAotProcessor {
performAotProcessing();
}
- /**
- * Delete the source, resource, and class output directories.
- */
- protected void deleteExistingOutput() {
- deleteExistingOutput(this.sourceOutput, this.resourceOutput, this.classOutput);
- }
-
- private void deleteExistingOutput(Path... paths) {
- for (Path path : paths) {
- try {
- FileSystemUtils.deleteRecursively(path);
- }
- catch (IOException ex) {
- throw new UncheckedIOException("Failed to delete existing output in '%s'".formatted(path), ex);
- }
- }
- }
-
/**
* Perform ahead-of-time processing of Spring integration test classes.
* Code, resources, and generated classes are stored in the configured
@@ -124,43 +79,14 @@ public class TestAotProcessor {
* components used by the tests.
*/
protected void performAotProcessing() {
- TestClassScanner scanner = new TestClassScanner(Set.of(this.classpathRoots));
+ TestClassScanner scanner = new TestClassScanner(this.classpathRoots);
Stream> testClasses = scanner.scan();
- GeneratedFiles generatedFiles = new FileSystemGeneratedFiles(this::getRoot);
+ GeneratedFiles generatedFiles = createFileSystemGeneratedFiles();
TestContextAotGenerator generator = new TestContextAotGenerator(generatedFiles);
generator.processAheadOfTime(testClasses);
writeHints(generator.getRuntimeHints());
}
- private Path getRoot(Kind kind) {
- return switch (kind) {
- case SOURCE -> this.sourceOutput;
- case RESOURCE -> this.resourceOutput;
- case CLASS -> this.classOutput;
- };
- }
-
- private void writeHints(RuntimeHints hints) {
- FileNativeConfigurationWriter writer =
- new FileNativeConfigurationWriter(this.resourceOutput, this.groupId, this.artifactId);
- writer.write(hints);
- }
-
-
- public static void main(String[] args) {
- int requiredArgs = 6;
- Assert.isTrue(args.length >= requiredArgs, () ->
- "Usage: %s "
- .formatted(TestAotProcessor.class.getName()));
- Path[] classpathRoots = Arrays.stream(args[0].split(File.pathSeparator)).map(Paths::get).toArray(Path[]::new);
- Path sourceOutput = Paths.get(args[1]);
- Path resourceOutput = Paths.get(args[2]);
- Path classOutput = Paths.get(args[3]);
- String groupId = args[4];
- String artifactId = args[5];
- new TestAotProcessor(classpathRoots, sourceOutput, resourceOutput, classOutput, groupId, artifactId).process();
- }
-
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/TestAotProcessorTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/TestAotProcessorTests.java
index fdce41c3f3..c98c3ca2f2 100644
--- a/spring-test/src/test/java/org/springframework/test/context/aot/TestAotProcessorTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/TestAotProcessorTests.java
@@ -22,6 +22,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
@@ -58,14 +59,15 @@ class TestAotProcessorTests extends AbstractAotTests {
BasicSpringVintageTests.class
).forEach(testClass -> copy(testClass, classpathRoot));
- Path[] classpathRoots = { classpathRoot };
+ Set classpathRoots = Set.of(classpathRoot);
Path sourceOutput = tempDir.resolve("generated/sources");
Path resourceOutput = tempDir.resolve("generated/resources");
Path classOutput = tempDir.resolve("generated/classes");
String groupId = "org.example";
String artifactId = "app-tests";
- TestAotProcessor processor = new TestAotProcessor(classpathRoots, sourceOutput, resourceOutput, classOutput, groupId, artifactId);
+ TestAotProcessor processor =
+ new DemoTestAotProcessor(classpathRoots, sourceOutput, resourceOutput, classOutput, groupId, artifactId);
processor.process();
assertThat(findFiles(sourceOutput)).containsExactlyInAnyOrderElementsOf(expectedSourceFiles());
@@ -97,4 +99,13 @@ class TestAotProcessorTests extends AbstractAotTests {
return Arrays.stream(expectedSourceFilesForBasicSpringTests).map(Path::of).toList();
}
+
+ private static class DemoTestAotProcessor extends TestAotProcessor {
+
+ DemoTestAotProcessor(Set classpathRoots, Path sourceOutput, Path resourceOutput, Path classOutput,
+ String groupId, String artifactId) {
+ super(classpathRoots, sourceOutput, resourceOutput, classOutput, groupId, artifactId);
+ }
+
+ }
}