Add Maven and Gradle option for the loader implementation to use

Add properties to the Maven and Gradle plugins so that users can
switch between the two loader modules.

See gh-37669
pull/37640/head
Phillip Webb 1 year ago
parent a89057b7c7
commit 55b5610dd9

@ -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<Set<ResolvedArtifactResult>> resolvedArtifacts);
/**
* The loader implementation that should be used with the archive.
* @return the loader implementation
* @since 3.2.0
*/
@Input
@Optional
Property<LoaderImplementation> getLoaderImplementation();
}

@ -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;
}

@ -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

@ -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

@ -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<FileTreeElement> requiresUnpack,
Spec<FileTreeElement> exclusions, LaunchScriptConfiguration launchScript, Spec<FileCopyDetails> librarySpec,
Function<FileCopyDetails, ZipCompression> 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()) {

@ -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/")) {

@ -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())

@ -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<T extends Jar & BootArchive> {
}
}
@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");

@ -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
}

@ -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
}

@ -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])
}
}

@ -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());
}
/**

@ -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

@ -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

@ -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;
}
}

@ -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);
}
}

@ -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) -> {

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-with-classic-loader</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>@java.version@</maven.compiler.source>
<maven.compiler.target>@java.version@</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>@maven-jar-plugin.version@</version>
<configuration>
<archive>
<manifest>
<mainClass>some.random.Main</mainClass>
</manifest>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
<loaderImplementation>CLASSIC</loaderImplementation>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>@spring-framework.version@</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>@jakarta-servlet.version@</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

@ -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) {
}
}

@ -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 extends Packager> P getConfiguredPackager(Supplier<P> supplier) {
P packager = supplier.get();
packager.setLoaderImplementation(getLoaderImplementation());
packager.setLayoutFactory(getLayoutFactory());
packager.addMainClassTimeoutWarningListener(new LoggingMainClassTimeoutWarningListener(this::getLog));
packager.setMainClass(this.mainClass);

@ -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.

@ -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.

@ -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
}
Loading…
Cancel
Save