diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchive.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchive.java index 1f3875a45a..2da97a470c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchive.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchive.java @@ -33,6 +33,8 @@ import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; +import org.springframework.boot.loader.tools.LoaderImplementation; + /** * A Spring Boot "fat" archive task. * @@ -133,4 +135,13 @@ public interface BootArchive extends Task { */ void resolvedArtifacts(Provider> resolvedArtifacts); + /** + * The loader implementation that should be used with the archive. + * @return the loader implementation + * @since 3.2.0 + */ + @Input + @Optional + Property getLoaderImplementation(); + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java index 921e9f3d48..22b086248a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java @@ -42,6 +42,8 @@ import org.gradle.api.tasks.WorkResult; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.util.PatternSet; +import org.springframework.boot.loader.tools.LoaderImplementation; + /** * Support class for implementations of {@link BootArchive}. * @@ -116,12 +118,13 @@ class BootArchiveSupport { return (version != null) ? version : "unknown"; } - CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies) { - return createCopyAction(jar, resolvedDependencies, null, null); + CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, + LoaderImplementation loaderImplementation) { + return createCopyAction(jar, resolvedDependencies, loaderImplementation, null, null); } - CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, LayerResolver layerResolver, - String layerToolsLocation) { + CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, + LoaderImplementation loaderImplementation, LayerResolver layerResolver, String layerToolsLocation) { File output = jar.getArchiveFile().get().getAsFile(); Manifest manifest = jar.getManifest(); boolean preserveFileTimestamps = jar.isPreserveFileTimestamps(); @@ -136,7 +139,7 @@ class BootArchiveSupport { String encoding = jar.getMetadataCharset(); CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirMode, fileMode, includeDefaultLoader, layerToolsLocation, requiresUnpack, exclusions, launchScript, librarySpec, - compressionResolver, encoding, resolvedDependencies, layerResolver); + compressionResolver, encoding, resolvedDependencies, layerResolver, loaderImplementation); return jar.isReproducibleFileOrder() ? new ReproducibleOrderingCopyAction(action) : action; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java index 5cf51bb850..c76a95f1d6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java @@ -37,6 +37,8 @@ import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.bundling.Jar; import org.gradle.work.DisableCachingByDefault; +import org.springframework.boot.loader.tools.LoaderImplementation; + /** * A custom {@link Jar} task that produces a Spring Boot executable jar. * @@ -141,12 +143,14 @@ public abstract class BootJar extends Jar implements BootArchive { @Override protected CopyAction createCopyAction() { + LoaderImplementation loaderImplementation = getLoaderImplementation().getOrElse(LoaderImplementation.DEFAULT); if (!isLayeredDisabled()) { LayerResolver layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); String layerToolsLocation = this.layered.getIncludeLayerTools().get() ? LIB_DIRECTORY : null; - return this.support.createCopyAction(this, this.resolvedDependencies, layerResolver, layerToolsLocation); + return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, layerResolver, + layerToolsLocation); } - return this.support.createCopyAction(this, this.resolvedDependencies); + return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation); } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java index d697f00a1e..d3aa0eab86 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java @@ -37,6 +37,8 @@ import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.bundling.War; import org.gradle.work.DisableCachingByDefault; +import org.springframework.boot.loader.tools.LoaderImplementation; + /** * A custom {@link War} task that produces a Spring Boot executable war. * @@ -115,12 +117,14 @@ public abstract class BootWar extends War implements BootArchive { @Override protected CopyAction createCopyAction() { + LoaderImplementation loaderImplementation = getLoaderImplementation().getOrElse(LoaderImplementation.DEFAULT); if (!isLayeredDisabled()) { LayerResolver layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); String layerToolsLocation = this.layered.getIncludeLayerTools().get() ? LIB_DIRECTORY : null; - return this.support.createCopyAction(this, this.resolvedDependencies, layerResolver, layerToolsLocation); + return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, layerResolver, + layerToolsLocation); } - return this.support.createCopyAction(this, this.resolvedDependencies); + return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation); } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java index 8e3a57e435..1f35d482fb 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java @@ -57,6 +57,7 @@ import org.springframework.boot.loader.tools.JarModeLibrary; import org.springframework.boot.loader.tools.Layer; import org.springframework.boot.loader.tools.LayersIndex; import org.springframework.boot.loader.tools.LibraryCoordinates; +import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.boot.loader.tools.NativeImageArgFile; import org.springframework.boot.loader.tools.ReachabilityMetadataProperties; import org.springframework.util.Assert; @@ -111,11 +112,14 @@ class BootZipCopyAction implements CopyAction { private final LayerResolver layerResolver; + private final LoaderImplementation loaderImplementation; + BootZipCopyAction(File output, Manifest manifest, boolean preserveFileTimestamps, Integer dirMode, Integer fileMode, boolean includeDefaultLoader, String layerToolsLocation, Spec requiresUnpack, Spec exclusions, LaunchScriptConfiguration launchScript, Spec librarySpec, Function compressionResolver, String encoding, - ResolvedDependencies resolvedDependencies, LayerResolver layerResolver) { + ResolvedDependencies resolvedDependencies, LayerResolver layerResolver, + LoaderImplementation loaderImplementation) { this.output = output; this.manifest = manifest; this.preserveFileTimestamps = preserveFileTimestamps; @@ -131,6 +135,7 @@ class BootZipCopyAction implements CopyAction { this.encoding = encoding; this.resolvedDependencies = resolvedDependencies; this.layerResolver = layerResolver; + this.loaderImplementation = loaderImplementation; } @Override @@ -310,7 +315,8 @@ class BootZipCopyAction implements CopyAction { // Always write loader entries after META-INF directory (see gh-16698) return; } - LoaderZipEntries loaderEntries = new LoaderZipEntries(getTime(), getDirMode(), getFileMode()); + LoaderZipEntries loaderEntries = new LoaderZipEntries(getTime(), getDirMode(), getFileMode(), + BootZipCopyAction.this.loaderImplementation); this.writtenLoaderEntries = loaderEntries.writeTo(this.out); if (BootZipCopyAction.this.layerResolver != null) { for (String name : this.writtenLoaderEntries.getFiles()) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java index 7606a3d66a..8a7851f07c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java @@ -28,6 +28,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.gradle.api.file.FileTreeElement; +import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.util.StreamUtils; /** @@ -39,22 +40,26 @@ import org.springframework.util.StreamUtils; */ class LoaderZipEntries { + private final LoaderImplementation loaderImplementation; + private final Long entryTime; private final int dirMode; private final int fileMode; - LoaderZipEntries(Long entryTime, int dirMode, int fileMode) { + LoaderZipEntries(Long entryTime, int dirMode, int fileMode, LoaderImplementation loaderImplementation) { this.entryTime = entryTime; this.dirMode = dirMode; this.fileMode = fileMode; + this.loaderImplementation = (loaderImplementation != null) ? loaderImplementation + : LoaderImplementation.DEFAULT; } WrittenEntries writeTo(ZipArchiveOutputStream out) throws IOException { WrittenEntries written = new WrittenEntries(); try (ZipInputStream loaderJar = new ZipInputStream( - getClass().getResourceAsStream("/META-INF/loader/spring-boot-loader-classic.jar"))) { + getClass().getResourceAsStream("/" + this.loaderImplementation.getJarResourceName()))) { java.util.zip.ZipEntry entry = loaderJar.getNextEntry(); while (entry != null) { if (entry.isDirectory() && !entry.getName().equals("META-INF/")) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java index a01d3bd415..45c10a127d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java @@ -106,6 +106,16 @@ abstract class AbstractBootArchiveIntegrationTests { assertThat(firstHash).isEqualTo(secondHash); } + @TestTemplate + void classicLoader() throws IOException { + assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) + .isEqualTo(TaskOutcome.SUCCESS); + File jar = new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0]; + try (JarFile jarFile = new JarFile(jar)) { + assertThat(jarFile.getEntry("org/springframework/boot/loader/LaunchedURLClassLoader.class")).isNotNull(); + } + } + @TestTemplate void upToDateWhenBuiltTwice() { assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java index 17623f9ed4..3dffc00752 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java @@ -65,6 +65,7 @@ import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.gradle.junit.GradleProjectBuilder; import org.springframework.boot.loader.tools.DefaultLaunchScript; import org.springframework.boot.loader.tools.JarModeLibrary; +import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -279,6 +280,17 @@ abstract class AbstractBootArchiveTests { } } + @Test + void loaderIsWrittenToTheRootOfTheJarWhenUsingClassicLoader() throws IOException { + this.task.getMainClass().set("com.example.Main"); + this.task.getLoaderImplementation().set(LoaderImplementation.CLASSIC); + executeTask(); + try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { + assertThat(jarFile.getEntry("org/springframework/boot/loader/LaunchedURLClassLoader.class")).isNotNull(); + assertThat(jarFile.getEntry("org/springframework/boot/loader/")).isNotNull(); + } + } + @Test void unpackCommentIsAddedToEntryIdentifiedByAPattern() throws IOException { this.task.getMainClass().set("com.example.Main"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classicLoader.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classicLoader.gradle new file mode 100644 index 0000000000..2e9e26c99c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classicLoader.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' + loaderImplementation = org.springframework.boot.loader.tools.LoaderImplementation.CLASSIC +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classicLoader.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classicLoader.gradle new file mode 100644 index 0000000000..fd14cc1a64 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classicLoader.gradle @@ -0,0 +1,9 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +bootWar { + mainClass = 'com.example.Application' + loaderImplementation = org.springframework.boot.loader.tools.LoaderImplementation.CLASSIC +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/build.gradle index 80311be0ac..f7968f659d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/build.gradle @@ -13,6 +13,10 @@ configurations { extendsFrom dependencyManagement transitive = false } + loaderClassic { + extendsFrom dependencyManagement + transitive = false + } jarmode { extendsFrom dependencyManagement transitive = false @@ -36,7 +40,8 @@ dependencies { compileOnly("ch.qos.logback:logback-classic") - loader(project(":spring-boot-project:spring-boot-tools:spring-boot-loader-classic")) + loader(project(":spring-boot-project:spring-boot-tools:spring-boot-loader")) + loaderClassic(project(":spring-boot-project:spring-boot-tools:spring-boot-loader-classic")) jarmode(project(":spring-boot-project:spring-boot-tools:spring-boot-jarmode-layertools")) @@ -57,6 +62,21 @@ task reproducibleLoaderJar(type: Jar) { } reproducibleFileOrder = true preserveFileTimestamps = false + archiveFileName = "spring-boot-loader.jar" + destinationDirectory = file("${generatedResources}/META-INF/loader") +} + +task reproducibleLoaderClassicJar(type: Jar) { + dependsOn configurations.loaderClassic + from { + zipTree(configurations.loaderClassic.incoming.files.singleFile).matching { + exclude "META-INF/LICENSE.txt" + exclude "META-INF/NOTICE.txt" + exclude "META-INF/spring-boot.properties" + } + } + reproducibleFileOrder = true + preserveFileTimestamps = false archiveFileName = "spring-boot-loader-classic.jar" destinationDirectory = file("${generatedResources}/META-INF/loader") } @@ -72,7 +92,7 @@ task layerToolsJar(type: Sync) { sourceSets { main { - output.dir(generatedResources, builtBy: [layerToolsJar, reproducibleLoaderJar]) + output.dir(generatedResources, builtBy: [layerToolsJar, reproducibleLoaderJar, reproducibleLoaderClassicJar]) } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java index 23282dbea7..7e807b1f9d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java @@ -51,8 +51,6 @@ import org.apache.commons.compress.archivers.zip.UnixStat; */ public abstract class AbstractJarWriter implements LoaderClassesWriter { - private static final String NESTED_LOADER_JAR = "META-INF/loader/spring-boot-loader-classic.jar"; - private static final int BUFFER_SIZE = 32 * 1024; private static final int UNIX_FILE_MODE = UnixStat.FILE_FLAG | UnixStat.DEFAULT_FILE_PERM; @@ -199,13 +197,15 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter { return library.getLastModified(); } - /** - * Write the required spring-boot-loader classes to the JAR. - * @throws IOException if the classes cannot be written - */ @Override public void writeLoaderClasses() throws IOException { - writeLoaderClasses(NESTED_LOADER_JAR); + writeLoaderClasses(LoaderImplementation.DEFAULT); + } + + @Override + public void writeLoaderClasses(LoaderImplementation loaderImplementation) throws IOException { + writeLoaderClasses((loaderImplementation != null) ? loaderImplementation.getJarResourceName() + : LoaderImplementation.DEFAULT.getJarResourceName()); } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java index 82c73ef859..e6f99282a7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java @@ -23,7 +23,7 @@ import java.util.Locale; import java.util.Map; /** - * Common {@link Layout}s. + * Common {@link Layout layouts}. * * @author Phillip Webb * @author Dave Syer diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderClassesWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderClassesWriter.java index 187ff0b902..864992279d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderClassesWriter.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderClassesWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-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. @@ -34,6 +34,14 @@ public interface LoaderClassesWriter { */ void writeLoaderClasses() throws IOException; + /** + * Write the default required spring-boot-loader classes to the JAR. + * @param loaderImplementation the specific implementation to write + * @throws IOException if the classes cannot be written + * @since 3.2.0 + */ + void writeLoaderClasses(LoaderImplementation loaderImplementation) throws IOException; + /** * Write custom required spring-boot-loader classes to the JAR. * @param loaderJarResourceName the name of the resource containing the loader classes diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderImplementation.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderImplementation.java new file mode 100644 index 0000000000..6414a3cfbb --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderImplementation.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-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.boot.loader.tools; + +/** + * Supported loader implementations. + * + * @author Phillip Webb + * @since 3.2.0 + */ +public enum LoaderImplementation { + + /** + * The default recommended loader implementation. + */ + DEFAULT("META-INF/loader/spring-boot-loader.jar"), + + /** + * The classic loader implementation as used with Spring Boot 3.1 and earlier. + */ + CLASSIC("META-INF/loader/spring-boot-loader-classic.jar"); + + private final String jarResourceName; + + LoaderImplementation(String jarResourceName) { + this.jarResourceName = jarResourceName; + } + + /** + * Return the name of the nested resource that can be loaded from the tools jar. + * @return the jar resource name + */ + public String getJarResourceName() { + return this.jarResourceName; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java index 815e0ee4a9..af4dff2330 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java @@ -88,6 +88,8 @@ public abstract class Packager { private Layout layout; + private LoaderImplementation loaderImplementation; + private LayoutFactory layoutFactory; private Layers layers; @@ -135,6 +137,14 @@ public abstract class Packager { this.layout = layout; } + /** + * Sets the loader implementation to use. + * @param loaderImplementation the loaderImplementation to set + */ + public void setLoaderImplementation(LoaderImplementation loaderImplementation) { + this.loaderImplementation = loaderImplementation; + } + /** * Sets the layout factory for the jar. The factory can be used when no specific * layout is specified. @@ -215,7 +225,7 @@ public abstract class Packager { customLoaderLayout.writeLoadedClasses(writer); } else if (layout.isExecutable()) { - writer.writeLoaderClasses(); + writer.writeLoaderClasses(this.loaderImplementation); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java index 1d37949cfa..c903da23a2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java @@ -75,6 +75,26 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests { }); } + @TestTemplate + void whenJarWithClassicLoaderIsRepackagedInPlaceOnlyRepackagedJarIsInstalled(MavenBuild mavenBuild) { + mavenBuild.project("jar-with-classic-loader").goals("install").execute((project) -> { + File original = new File(project, "target/jar-with-classic-loader-0.0.1.BUILD-SNAPSHOT.jar.original"); + assertThat(original).isFile(); + File repackaged = new File(project, "target/jar-with-classic-loader-0.0.1.BUILD-SNAPSHOT.jar"); + assertThat(launchScript(repackaged)).isEmpty(); + assertThat(jar(repackaged)).manifest((manifest) -> { + manifest.hasMainClass("org.springframework.boot.loader.launch.JarLauncher"); + manifest.hasStartClass("some.random.Main"); + manifest.hasAttribute("Not-Used", "Foo"); + }).hasEntryWithName("org/springframework/boot/loader/launch/JarLauncher.class"); + assertThat(buildLog(project)) + .contains("Replacing main artifact " + repackaged + " with repackaged archive,") + .contains("The original artifact has been renamed to " + original) + .contains("Installing " + repackaged + " to") + .doesNotContain("Installing " + original + " to"); + }); + } + @TestTemplate void whenAttachIsDisabledOnlyTheOriginalJarIsInstalled(MavenBuild mavenBuild) { mavenBuild.project("jar-attach-disabled").goals("install").execute((project) -> { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/pom.xml b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/pom.xml new file mode 100644 index 0000000000..64d9d04f99 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/pom.xml @@ -0,0 +1,58 @@ + + + 4.0.0 + org.springframework.boot.maven.it + jar-with-classic-loader + 0.0.1.BUILD-SNAPSHOT + + UTF-8 + @java.version@ + @java.version@ + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + + repackage + + + + + + org.apache.maven.plugins + maven-jar-plugin + @maven-jar-plugin.version@ + + + + some.random.Main + + + Foo + + + CLASSIC + + + + + + + org.springframework + spring-context + @spring-framework.version@ + + + jakarta.servlet + jakarta.servlet-api + @jakarta-servlet.version@ + provided + + + diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/src/main/java/org/test/SampleApplication.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/src/main/java/org/test/SampleApplication.java new file mode 100644 index 0000000000..5e51546d4e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/src/main/java/org/test/SampleApplication.java @@ -0,0 +1,24 @@ +/* + * Copyright 2012-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.test; + +public class SampleApplication { + + public static void main(String[] args) { + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java index b6dfdc04bb..28d55d213a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java @@ -47,6 +47,7 @@ import org.springframework.boot.loader.tools.Layouts.Jar; import org.springframework.boot.loader.tools.Layouts.None; import org.springframework.boot.loader.tools.Layouts.War; import org.springframework.boot.loader.tools.Libraries; +import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.boot.loader.tools.Packager; import org.springframework.boot.loader.tools.layer.CustomLayers; @@ -128,6 +129,15 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo return null; } + /** + * Return the loader implementation that should be used. + * @return the loader implementation or {@code null} + * @since 3.2.0 + */ + protected LoaderImplementation getLoaderImplementation() { + return null; + } + /** * Return the layout factory that will be used to determine the {@link LayoutType} if * no explicit layout is set. @@ -145,6 +155,7 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo */ protected

P getConfiguredPackager(Supplier

supplier) { P packager = supplier.get(); + packager.setLoaderImplementation(getLoaderImplementation()); packager.setLayoutFactory(getLayoutFactory()); packager.addMainClassTimeoutWarningListener(new LoggingMainClassTimeoutWarningListener(this::getLog)); packager.setMainClass(this.mainClass); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java index 84589c0189..79b62bf530 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java @@ -48,6 +48,7 @@ import org.springframework.boot.loader.tools.EntryWriter; import org.springframework.boot.loader.tools.ImagePackager; import org.springframework.boot.loader.tools.LayoutFactory; import org.springframework.boot.loader.tools.Libraries; +import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.util.StringUtils; /** @@ -187,6 +188,13 @@ public abstract class BuildImageMojo extends AbstractPackagerMojo { @Parameter private LayoutType layout; + /** + * The loader implementation that should be used. + * @since 3.2.0 + */ + @Parameter + private LoaderImplementation loaderImplementation; + /** * The layout factory that will be used to create the executable archive if no * explicit layout is set. Alternative layouts implementations can be provided by 3rd @@ -206,6 +214,11 @@ public abstract class BuildImageMojo extends AbstractPackagerMojo { return this.layout; } + @Override + protected LoaderImplementation getLoaderImplementation() { + return this.loaderImplementation; + } + /** * Return the layout factory that will be used to determine the * {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java index 5ca2ecac5a..13a16c2a14 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java @@ -36,6 +36,7 @@ import org.springframework.boot.loader.tools.DefaultLaunchScript; import org.springframework.boot.loader.tools.LaunchScript; import org.springframework.boot.loader.tools.LayoutFactory; import org.springframework.boot.loader.tools.Libraries; +import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.boot.loader.tools.Repackager; /** @@ -161,6 +162,13 @@ public class RepackageMojo extends AbstractPackagerMojo { @Parameter(property = "spring-boot.repackage.layout") private LayoutType layout; + /** + * The loader implementation that should be used. + * @since 3.2.0 + */ + @Parameter + private LoaderImplementation loaderImplementation; + /** * The layout factory that will be used to create the executable archive if no * explicit layout is set. Alternative layouts implementations can be provided by 3rd @@ -180,6 +188,11 @@ public class RepackageMojo extends AbstractPackagerMojo { return this.layout; } + @Override + protected LoaderImplementation getLoaderImplementation() { + return this.loaderImplementation; + } + /** * Return the layout factory that will be used to determine the * {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set. diff --git a/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-classic-tests/spring-boot-loader-classic-tests-app/build.gradle b/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-classic-tests/spring-boot-loader-classic-tests-app/build.gradle index 37596c6206..16f7a57ebe 100644 --- a/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-classic-tests/spring-boot-loader-classic-tests-app/build.gradle +++ b/spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-classic-tests/spring-boot-loader-classic-tests-app/build.gradle @@ -16,3 +16,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.webjars:jquery:3.5.0") } + +bootJar { + loaderImplementation = org.springframework.boot.loader.tools.LoaderImplementation.CLASSIC +} \ No newline at end of file