Commit c6c6524b authored by Dave Syer's avatar Dave Syer Committed by Phillip Webb

Support custom fat jar layouts

Allow support for custom Lyout implementations with both the Maven
and Gradle plugin. Implementations of `LayoutFactory` can now be
specified to allow customization of the layout. In addition a
layout may now implement `CustomLoaderLayout` if it wishes to
write custom loader classes.

See gh-7263
parent f5b03c81
...@@ -484,6 +484,11 @@ The following configuration options are available: ...@@ -484,6 +484,11 @@ The following configuration options are available:
(defaults to a guess based on the archive type). See (defaults to a guess based on the archive type). See
<<build-tool-plugins-gradle-configuration-layouts,available layouts for more details>>. <<build-tool-plugins-gradle-configuration-layouts,available layouts for more details>>.
|'layoutFactory`
|A layout factory that can be used if a custom layout is required. Alternative layouts
can be provided by 3rd parties. Layout factories are only used when `layout` is not
specified.
|`requiresUnpack` |`requiresUnpack`
|A list of dependencies (in the form "`groupId:artifactId`" that must be unpacked from |A list of dependencies (in the form "`groupId:artifactId`" that must be unpacked from
fat jars in order to run. Items are still packaged into the fat jar, but they will be fat jars in order to run. Items are still packaged into the fat jar, but they will be
...@@ -530,6 +535,38 @@ loader should be included or not. The following layouts are available: ...@@ -530,6 +535,38 @@ loader should be included or not. The following layouts are available:
+[[build-tool-plugins-gradle-configuration-custom-repackager]]
+==== Using a custom layout
If you have custom requirements for how to arrange the dependencies and loader classes
inside the repackaged jar, you can use a custom layout. Any library which defines one
or more `LayoutFactory` implementations can be added to the build script dependencies
and then the layout factory becomes available in the `springBoot` configuration.
For example:
[source,groovy,indent=0,subs="verbatim,attributes"]
----
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:{spring-boot-version}")
classpath("com.example:custom-layout:1.0.0")
}
}
springBoot {
layoutFactory = new com.example.CustomLayoutFactory()
}
+----
NOTE: If there is only one custom `LayoutFactory` on the build classpath and it is
listed in `META-INF/spring.factories` then it is unnecessary to explicitly set it in the
`springBoot` configuration. Layout factories are only used when no explicit `layout` is
specified.
[[build-tool-plugins-understanding-the-gradle-plugin]] [[build-tool-plugins-understanding-the-gradle-plugin]]
=== Understanding how the Gradle plugin works === Understanding how the Gradle plugin works
When `spring-boot` is applied to your Gradle project a default task named `bootRepackage` When `spring-boot` is applied to your Gradle project a default task named `bootRepackage`
......
...@@ -26,6 +26,7 @@ import org.gradle.api.plugins.JavaPlugin; ...@@ -26,6 +26,7 @@ import org.gradle.api.plugins.JavaPlugin;
import org.springframework.boot.gradle.buildinfo.BuildInfo; import org.springframework.boot.gradle.buildinfo.BuildInfo;
import org.springframework.boot.loader.tools.Layout; import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.LayoutFactory;
import org.springframework.boot.loader.tools.Layouts; import org.springframework.boot.loader.tools.Layouts;
/** /**
...@@ -90,6 +91,12 @@ public class SpringBootPluginExtension { ...@@ -90,6 +91,12 @@ public class SpringBootPluginExtension {
*/ */
LayoutType layout; LayoutType layout;
/**
* The layout factory that will be used when no explicit layout is specified.
* Alternative layouts can be provided by 3rd parties.
*/
LayoutFactory layoutFactory;
/** /**
* Libraries that must be unpacked from fat jars in order to run. Use Strings in the * Libraries that must be unpacked from fat jars in order to run. Use Strings in the
* form {@literal groupId:artifactId}. * form {@literal groupId:artifactId}.
...@@ -196,6 +203,14 @@ public class SpringBootPluginExtension { ...@@ -196,6 +203,14 @@ public class SpringBootPluginExtension {
this.layout = layout; this.layout = layout;
} }
public LayoutFactory getLayoutFactory() {
return this.layoutFactory;
}
public void setLayoutFactory(LayoutFactory layoutFactory) {
this.layoutFactory = layoutFactory;
}
public Set<String> getRequiresUnpack() { public Set<String> getRequiresUnpack() {
return this.requiresUnpack; return this.requiresUnpack;
} }
......
...@@ -213,7 +213,8 @@ public class RepackageTask extends DefaultTask { ...@@ -213,7 +213,8 @@ public class RepackageTask extends DefaultTask {
copy(file, outputFile); copy(file, outputFile);
file = outputFile; file = outputFile;
} }
Repackager repackager = new Repackager(file); Repackager repackager = new Repackager(file,
this.extension.getLayoutFactory());
repackager.addMainClassTimeoutWarningListener( repackager.addMainClassTimeoutWarningListener(
new LoggingMainClassTimeoutWarningListener()); new LoggingMainClassTimeoutWarningListener());
setMainClass(repackager); setMainClass(repackager);
......
/*
* Copyright 2012-2016 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
*
* http://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;
import java.io.IOException;
/**
* Additional interface that can be implemented by {@link Layout Layouts} that write their
* own loader classes.
*
* @author Phillip Webb
* @since 1.5.0
*/
public interface CustomLoaderLayout {
/**
* Write the required loader classes into the JAR.
* @param writer the writer used to write the classes
* @throws IOException if the classes cannot be written
*/
void writeLoadedClasses(LoaderClassesWriter writer) throws IOException;
}
/*
* Copyright 2012-2016 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
*
* http://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;
import java.io.File;
/**
* Default implementation of {@link LayoutFactory}.
*
* @author Phillip Webb
* @since 1.5.0
*/
public class DefaultLayoutFactory implements LayoutFactory {
@Override
public Layout getLayout(File source) {
return Layouts.forFile(source);
}
}
...@@ -51,7 +51,7 @@ import org.springframework.lang.UsesJava7; ...@@ -51,7 +51,7 @@ import org.springframework.lang.UsesJava7;
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class JarWriter { public class JarWriter implements LoaderClassesWriter {
private static final String NESTED_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar"; private static final String NESTED_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar";
...@@ -158,6 +158,7 @@ public class JarWriter { ...@@ -158,6 +158,7 @@ public class JarWriter {
* @param inputStream The stream from which the entry's data can be read * @param inputStream The stream from which the entry's data can be read
* @throws IOException if the write fails * @throws IOException if the write fails
*/ */
@Override
public void writeEntry(String entryName, InputStream inputStream) throws IOException { public void writeEntry(String entryName, InputStream inputStream) throws IOException {
JarEntry entry = new JarEntry(entryName); JarEntry entry = new JarEntry(entryName);
writeEntry(entry, new InputStreamEntryWriter(inputStream, true)); writeEntry(entry, new InputStreamEntryWriter(inputStream, true));
...@@ -207,8 +208,20 @@ public class JarWriter { ...@@ -207,8 +208,20 @@ public class JarWriter {
* Write the required spring-boot-loader classes to the JAR. * Write the required spring-boot-loader classes to the JAR.
* @throws IOException if the classes cannot be written * @throws IOException if the classes cannot be written
*/ */
@Override
public void writeLoaderClasses() throws IOException { public void writeLoaderClasses() throws IOException {
URL loaderJar = getClass().getClassLoader().getResource(NESTED_LOADER_JAR); writeLoaderClasses(NESTED_LOADER_JAR);
}
/**
* Write the required spring-boot-loader classes to the JAR.
* @param loaderJarResourceName the name of the resource containing the loader classes
* to be written
* @throws IOException if the classes cannot be written
*/
@Override
public void writeLoaderClasses(String loaderJarResourceName) throws IOException {
URL loaderJar = getClass().getClassLoader().getResource(loaderJarResourceName);
JarInputStream inputStream = new JarInputStream( JarInputStream inputStream = new JarInputStream(
new BufferedInputStream(loaderJar.openStream())); new BufferedInputStream(loaderJar.openStream()));
JarEntry entry; JarEntry entry;
......
...@@ -18,9 +18,13 @@ package org.springframework.boot.loader.tools; ...@@ -18,9 +18,13 @@ package org.springframework.boot.loader.tools;
/** /**
* Strategy interface used to determine the layout for a particular type of archive. * Strategy interface used to determine the layout for a particular type of archive.
* Layouts may additionally implement {@link CustomLoaderLayout} if they wish to write
* custom loader classes.
* *
* @author Phillip Webb * @author Phillip Webb
* @see Layouts * @see Layouts
* @see RepackagingLayout
* @see CustomLoaderLayout
*/ */
public interface Layout { public interface Layout {
......
/*
* Copyright 2012-2016 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
*
* http://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;
import java.io.File;
/**
* Factory interface used to create a {@link Layout}.
*
* @author Dave Syer
* @author Phillip Webb
*/
public interface LayoutFactory {
/**
* Return a {@link Layout} for the specified source file.
* @param source the source file
* @return the layout to use for the file
*/
Layout getLayout(File source);
}
/*
* Copyright 2012-2016 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
*
* http://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;
import java.io.IOException;
import java.io.InputStream;
/**
* Writer used by {@link CustomLoaderLayout CustomLoaderLayouts} to write classes into a
* repackaged JAR.
*
* @author Phillip Webb
* @since 1.5.0
*/
public interface LoaderClassesWriter {
/**
* Write the default required spring-boot-loader classes to the JAR.
* @throws IOException if the classes cannot be written
*/
void writeLoaderClasses() throws IOException;
/**
* Write custom required spring-boot-loader classes to the JAR.
* @param loaderJarResourceName the name of the resource containing the loader classes
* to be written
* @throws IOException if the classes cannot be written
*/
void writeLoaderClasses(String loaderJarResourceName) throws IOException;
/**
* Write a single entry to the JAR.
* @param name the name of the entry
* @param inputStream the input stream content
* @throws IOException if the entry cannot be written
*/
void writeEntry(String name, InputStream inputStream) throws IOException;
}
...@@ -30,7 +30,10 @@ import java.util.jar.JarFile; ...@@ -30,7 +30,10 @@ import java.util.jar.JarFile;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import org.springframework.boot.loader.tools.JarWriter.EntryTransformer; import org.springframework.boot.loader.tools.JarWriter.EntryTransformer;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.lang.UsesJava8; import org.springframework.lang.UsesJava8;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/** /**
* Utility class that can be used to repackage an archive so that it can be executed using * Utility class that can be used to repackage an archive so that it can be executed using
...@@ -66,12 +69,18 @@ public class Repackager { ...@@ -66,12 +69,18 @@ public class Repackager {
private Layout layout; private Layout layout;
private LayoutFactory layoutFactory;
public Repackager(File source) { public Repackager(File source) {
this(source, null);
}
public Repackager(File source, LayoutFactory layoutFactory) {
if (source == null || !source.exists() || !source.isFile()) { if (source == null || !source.exists() || !source.isFile()) {
throw new IllegalArgumentException("Source must refer to an existing file"); throw new IllegalArgumentException("Source must refer to an existing file");
} }
this.source = source.getAbsoluteFile(); this.source = source.getAbsoluteFile();
this.layout = Layouts.forFile(source); this.layoutFactory = layoutFactory;
} }
/** /**
...@@ -113,6 +122,15 @@ public class Repackager { ...@@ -113,6 +122,15 @@ public class Repackager {
this.layout = layout; this.layout = layout;
} }
/**
* Sets the layout factory for the jar. The factory can be used when no specific
* layout is specific.
* @param layoutFactory the layoutFactory to set
*/
public void setLayoutFactory(LayoutFactory layoutFactory) {
this.layoutFactory = layoutFactory;
}
/** /**
* Repackage the source file so that it can be run using '{@literal java -jar}'. * Repackage the source file so that it can be run using '{@literal java -jar}'.
* @param libraries the libraries required to run the archive * @param libraries the libraries required to run the archive
...@@ -150,6 +168,9 @@ public class Repackager { ...@@ -150,6 +168,9 @@ public class Repackager {
if (libraries == null) { if (libraries == null) {
throw new IllegalArgumentException("Libraries must not be null"); throw new IllegalArgumentException("Libraries must not be null");
} }
if (this.layout == null) {
this.layout = getLayoutFactory().getLayout(this.source);
}
if (alreadyRepackaged()) { if (alreadyRepackaged()) {
return; return;
} }
...@@ -177,6 +198,19 @@ public class Repackager { ...@@ -177,6 +198,19 @@ public class Repackager {
} }
} }
private LayoutFactory getLayoutFactory() {
if (this.layoutFactory != null) {
return this.layoutFactory;
}
List<LayoutFactory> factories = SpringFactoriesLoader
.loadFactories(LayoutFactory.class, null);
if (factories.isEmpty()) {
return new DefaultLayoutFactory();
}
Assert.state(factories.size() == 1, "No unique LayoutFactory found");
return factories.get(0);
}
/** /**
* Return the {@link File} to use to backup the original source. * Return the {@link File} to use to backup the original source.
* @return the file to use to backup the original source * @return the file to use to backup the original source
...@@ -219,21 +253,7 @@ public class Repackager { ...@@ -219,21 +253,7 @@ public class Repackager {
} }
}); });
writer.writeManifest(buildManifest(sourceJar)); repackage(sourceJar, writer, unpackLibraries, standardLibraries);
Set<String> seen = new HashSet<String>();
writeNestedLibraries(unpackLibraries, seen, writer);
if (this.layout instanceof RepackagingLayout) {
writer.writeEntries(sourceJar,
new RenamingEntryTransformer(((RepackagingLayout) this.layout)
.getRepackagedClassesLocation()));
}
else {
writer.writeEntries(sourceJar);
}
writeNestedLibraries(standardLibraries, seen, writer);
if (this.layout.isExecutable()) {
writer.writeLoaderClasses();
}
} }
finally { finally {
try { try {
...@@ -245,6 +265,23 @@ public class Repackager { ...@@ -245,6 +265,23 @@ public class Repackager {
} }
} }
private void repackage(JarFile sourceJar, JarWriter writer,
final List<Library> unpackLibraries, final List<Library> standardLibraries)
throws IOException {
writer.writeManifest(buildManifest(sourceJar));
Set<String> seen = new HashSet<String>();
writeNestedLibraries(unpackLibraries, seen, writer);
if (this.layout instanceof RepackagingLayout) {
writer.writeEntries(sourceJar, new RenamingEntryTransformer(
((RepackagingLayout) this.layout).getRepackagedClassesLocation()));
}
else {
writer.writeEntries(sourceJar);
}
writeNestedLibraries(standardLibraries, seen, writer);
writeLoaderClasses(writer);
}
private void writeNestedLibraries(List<Library> libraries, Set<String> alreadySeen, private void writeNestedLibraries(List<Library> libraries, Set<String> alreadySeen,
JarWriter writer) throws IOException { JarWriter writer) throws IOException {
for (Library library : libraries) { for (Library library : libraries) {
...@@ -260,6 +297,15 @@ public class Repackager { ...@@ -260,6 +297,15 @@ public class Repackager {
} }
} }
private void writeLoaderClasses(JarWriter writer) throws IOException {
if (this.layout instanceof CustomLoaderLayout) {
((CustomLoaderLayout) this.layout).writeLoadedClasses(writer);
}
else if (this.layout.isExecutable()) {
writer.writeLoaderClasses();
}
}
private boolean isZip(File file) { private boolean isZip(File file) {
try { try {
FileInputStream fileInputStream = new FileInputStream(file); FileInputStream fileInputStream = new FileInputStream(file);
...@@ -316,8 +362,10 @@ public class Repackager { ...@@ -316,8 +362,10 @@ public class Repackager {
(this.layout instanceof RepackagingLayout) (this.layout instanceof RepackagingLayout)
? ((RepackagingLayout) this.layout).getRepackagedClassesLocation() ? ((RepackagingLayout) this.layout).getRepackagedClassesLocation()
: this.layout.getClassesLocation()); : this.layout.getClassesLocation());
manifest.getMainAttributes().putValue(BOOT_LIB_ATTRIBUTE, String lib = this.layout.getLibraryDestination("", LibraryScope.COMPILE);
this.layout.getLibraryDestination("", LibraryScope.COMPILE)); if (StringUtils.hasLength(lib)) {
manifest.getMainAttributes().putValue(BOOT_LIB_ATTRIBUTE, lib);
}
return manifest; return manifest;
} }
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.loader.tools; package org.springframework.boot.loader.tools;
import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
...@@ -347,14 +348,46 @@ public class RepackagerTests { ...@@ -347,14 +348,46 @@ public class RepackagerTests {
final LibraryScope scope = mock(LibraryScope.class); final LibraryScope scope = mock(LibraryScope.class);
given(layout.getLauncherClassName()).willReturn("testLauncher"); given(layout.getLauncherClassName()).willReturn("testLauncher");
given(layout.getLibraryDestination(anyString(), eq(scope))).willReturn("test/"); given(layout.getLibraryDestination(anyString(), eq(scope))).willReturn("test/");
given(layout.getLibraryDestination(anyString(), eq(LibraryScope.COMPILE)))
.willReturn("test-lib/");
repackager.setLayout(layout); repackager.setLayout(layout);
repackager.repackage(new Libraries() { repackager.repackage(new Libraries() {
@Override @Override
public void doWithLibraries(LibraryCallback callback) throws IOException { public void doWithLibraries(LibraryCallback callback) throws IOException {
callback.library(new Library(libJarFile, scope)); callback.library(new Library(libJarFile, scope));
} }
}); });
assertThat(hasEntry(file, "test/" + libJarFile.getName())).isTrue(); assertThat(hasEntry(file, "test/" + libJarFile.getName())).isTrue();
assertThat(getManifest(file).getMainAttributes().getValue("Spring-Boot-Lib"))
.isEqualTo("test-lib/");
assertThat(getManifest(file).getMainAttributes().getValue("Main-Class"))
.isEqualTo("testLauncher");
}
@Test
public void customLayoutNoBootLib() throws Exception {
TestJarFile libJar = new TestJarFile(this.temporaryFolder);
libJar.addClass("a/b/C.class", ClassWithoutMainMethod.class);
final File libJarFile = libJar.getFile();
this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class);
File file = this.testJarFile.getFile();
Repackager repackager = new Repackager(file);
Layout layout = mock(Layout.class);
final LibraryScope scope = mock(LibraryScope.class);
given(layout.getLauncherClassName()).willReturn("testLauncher");
repackager.setLayout(layout);
repackager.repackage(new Libraries() {
@Override
public void doWithLibraries(LibraryCallback callback) throws IOException {
callback.library(new Library(libJarFile, scope));
}
});
assertThat(getManifest(file).getMainAttributes().getValue("Spring-Boot-Lib"))
.isNull();
assertThat(getManifest(file).getMainAttributes().getValue("Main-Class")) assertThat(getManifest(file).getMainAttributes().getValue("Main-Class"))
.isEqualTo("testLauncher"); .isEqualTo("testLauncher");
} }
...@@ -536,6 +569,29 @@ public class RepackagerTests { ...@@ -536,6 +569,29 @@ public class RepackagerTests {
} }
} }
@Test
public void customLayoutFactoryWithoutLayout() throws Exception {
this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class);
File source = this.testJarFile.getFile();
Repackager repackager = new Repackager(source, new TestLayoutFactory());
repackager.repackage(NO_LIBRARIES);
JarFile jarFile = new JarFile(source);
assertThat(jarFile.getEntry("test")).isNotNull();
jarFile.close();
}
@Test
public void customLayoutFactoryWithLayout() throws Exception {
this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class);
File source = this.testJarFile.getFile();
Repackager repackager = new Repackager(source, new TestLayoutFactory());
repackager.setLayout(new Layouts.Jar());
repackager.repackage(NO_LIBRARIES);
JarFile jarFile = new JarFile(source);
assertThat(jarFile.getEntry("test")).isNull();
jarFile.close();
}
private boolean hasLauncherClasses(File file) throws IOException { private boolean hasLauncherClasses(File file) throws IOException {
return hasEntry(file, "org/springframework/boot/") return hasEntry(file, "org/springframework/boot/")
&& hasEntry(file, "org/springframework/boot/loader/JarLauncher.class"); && hasEntry(file, "org/springframework/boot/loader/JarLauncher.class");
...@@ -580,4 +636,22 @@ public class RepackagerTests { ...@@ -580,4 +636,22 @@ public class RepackagerTests {
} }
public static class TestLayoutFactory implements LayoutFactory {
@Override
public Layout getLayout(File source) {
return new TestLayout();
}
}
private static class TestLayout extends Layouts.Jar implements CustomLoaderLayout {
@Override
public void writeLoadedClasses(LoaderClassesWriter writer) throws IOException {
writer.writeEntry("test", new ByteArrayInputStream("test".getBytes()));
}
}
} }
...@@ -40,6 +40,7 @@ import org.apache.maven.shared.artifact.filter.collection.ScopeFilter; ...@@ -40,6 +40,7 @@ import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
import org.springframework.boot.loader.tools.DefaultLaunchScript; import org.springframework.boot.loader.tools.DefaultLaunchScript;
import org.springframework.boot.loader.tools.LaunchScript; import org.springframework.boot.loader.tools.LaunchScript;
import org.springframework.boot.loader.tools.Layout; import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.LayoutFactory;
import org.springframework.boot.loader.tools.Layouts; import org.springframework.boot.loader.tools.Layouts;
import org.springframework.boot.loader.tools.Libraries; import org.springframework.boot.loader.tools.Libraries;
import org.springframework.boot.loader.tools.Repackager; import org.springframework.boot.loader.tools.Repackager;
...@@ -129,6 +130,15 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { ...@@ -129,6 +130,15 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
@Parameter @Parameter
private LayoutType layout; private LayoutType layout;
/**
* 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
* parties.
* @since 1.5
*/
@Parameter
private LayoutFactory layoutFactory;
/** /**
* A list of the libraries that must be unpacked from fat jars in order to run. * A list of the libraries that must be unpacked from fat jars in order to run.
* Specify each library as a <code>&lt;dependency&gt;</code> with a * Specify each library as a <code>&lt;dependency&gt;</code> with a
...@@ -220,7 +230,7 @@ public class RepackageMojo extends AbstractDependencyFilterMojo { ...@@ -220,7 +230,7 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
} }
private Repackager getRepackager(File source) { private Repackager getRepackager(File source) {
Repackager repackager = new Repackager(source); Repackager repackager = new Repackager(source, this.layoutFactory);
repackager.addMainClassTimeoutWarningListener( repackager.addMainClassTimeoutWarningListener(
new LoggingMainClassTimeoutWarningListener()); new LoggingMainClassTimeoutWarningListener());
repackager.setMainClass(this.mainClass); repackager.setMainClass(this.mainClass);
......
-----
Use a custom layout
-----
Dave Syer
-----
2016-10-30
-----
Spring Boot repackages the jar file for this project using a custom layout factory
defined in the additional jar file, provided as a dependency to the build plugin:
---
<project>
...
<build>
...
<plugins>
...
<plugin>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<layoutFactory implementation="com.example.CustomLayoutFactory">
<customProperty>value</customProperty>
</layoutFactory>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.example</groupid>
<artifactId>custom-layout</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
</dependency>
</dependencies>
...
</plugin>
...
</plugins>
...
</build>
...
</project>
+---
The layout factory is provided as an implementation of <<<LayoutFactory>>> (from
spring-boot-loader-tools) explicitly specified in the pom. If there is only one custom
<<<LayoutFactory>>> on the plugin classpath and it is listed in
<<<META-INF/spring.factories>>> then it is unnecessary to explicitly set it in the
plugin configuration.
Layout factories are always ignored if an explicit <<layout>> is set.
...@@ -48,6 +48,8 @@ Spring Boot Maven Plugin ...@@ -48,6 +48,8 @@ Spring Boot Maven Plugin
* {{{./examples/repackage-disable-attach.html}Local repackaged artifact}} * {{{./examples/repackage-disable-attach.html}Local repackaged artifact}}
* {{{./examples/custom-layout.html}Custom layout}}
* {{{./examples/exclude-dependency.html}Exclude a dependency}} * {{{./examples/exclude-dependency.html}Exclude a dependency}}
* {{{./examples/run-debug.html}Debug the application}} * {{{./examples/run-debug.html}Debug the application}}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment