Polish loader and loader-tools

Polish and refactor `spring-boot-loader` and `spring-boot-loader-tools`
to make it easier to add indexing and layering support.

Closes gh-19766
This commit is contained in:
Phillip Webb
2020-01-15 21:11:07 -08:00
parent 56e30258fb
commit ad72f86bdb
11 changed files with 284 additions and 186 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@@ -32,6 +32,8 @@ import org.springframework.boot.loader.archive.Archive;
*/
public abstract class ExecutableArchiveLauncher extends Launcher {
private static final String START_CLASS_ATTRIBUTE = "Start-Class";
private final Archive archive;
public ExecutableArchiveLauncher() {
@@ -44,11 +46,12 @@ public abstract class ExecutableArchiveLauncher extends Launcher {
}
protected ExecutableArchiveLauncher(Archive archive) {
this.archive = archive;
}
protected final Archive getArchive() {
return this.archive;
try {
this.archive = archive;
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
@Override
@@ -56,7 +59,7 @@ public abstract class ExecutableArchiveLauncher extends Launcher {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue("Start-Class");
mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE);
}
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this);
@@ -125,4 +128,12 @@ public abstract class ExecutableArchiveLauncher extends Launcher {
return this.archive.supportsNestedJars();
}
/**
* Return the root archive.
* @return the root archive
*/
protected final Archive getArchive() {
return this.archive;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@@ -77,7 +77,9 @@ public abstract class Launcher {
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(50);
while (archives.hasNext()) {
urls.add(archives.next().getUrl());
Archive archive = archives.next();
urls.add(archive.getUrl());
archive.close();
}
return createClassLoader(urls.toArray(new URL[0]));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@@ -23,7 +23,7 @@ import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
@@ -53,7 +53,15 @@ public abstract class AbstractExecutableArchiveLauncherTests {
jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/"));
jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/classes/"));
jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/lib/"));
JarEntry libFoo = new JarEntry(entryPrefix + "/lib/foo.jar");
addNestedJars(entryPrefix, "/lib/foo.jar", jarOutputStream);
addNestedJars(entryPrefix, "/lib/bar.jar", jarOutputStream);
addNestedJars(entryPrefix, "/lib/baz.jar", jarOutputStream);
jarOutputStream.close();
return archive;
}
private void addNestedJars(String entryPrefix, String lib, JarOutputStream jarOutputStream) throws IOException {
JarEntry libFoo = new JarEntry(entryPrefix + lib);
libFoo.setMethod(ZipEntry.STORED);
ByteArrayOutputStream fooJarStream = new ByteArrayOutputStream();
new JarOutputStream(fooJarStream).close();
@@ -63,8 +71,6 @@ public abstract class AbstractExecutableArchiveLauncherTests {
libFoo.setCrc(crc32.getValue());
jarOutputStream.putNextEntry(libFoo);
jarOutputStream.write(fooJarStream.toByteArray());
jarOutputStream.close();
return archive;
}
protected File explode(File archive) throws IOException {
@@ -87,11 +93,20 @@ public abstract class AbstractExecutableArchiveLauncherTests {
}
protected Set<URL> getUrls(List<Archive> archives) throws MalformedURLException {
Set<URL> urls = new HashSet<>(archives.size());
Set<URL> urls = new LinkedHashSet<>(archives.size());
for (Archive archive : archives) {
urls.add(archive.getUrl());
}
return urls;
}
protected final URL toUrl(File file) {
try {
return file.toURI().toURL();
}
catch (MalformedURLException ex) {
throw new IllegalStateException(ex);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@@ -42,9 +42,7 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests {
JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true));
List<Archive> archives = new ArrayList<>();
launcher.getClassPathArchivesIterator().forEachRemaining(archives::add);
assertThat(archives).hasSize(2);
assertThat(getUrls(archives)).containsOnly(new File(explodedRoot, "BOOT-INF/classes").toURI().toURL(),
new File(explodedRoot, "BOOT-INF/lib/foo.jar").toURI().toURL());
assertThat(getUrls(archives)).containsExactlyInAnyOrder(getExpectedFileUrls(explodedRoot));
for (Archive archive : archives) {
archive.close();
}
@@ -57,14 +55,29 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests {
JarLauncher launcher = new JarLauncher(archive);
List<Archive> classPathArchives = new ArrayList<>();
launcher.getClassPathArchivesIterator().forEachRemaining(classPathArchives::add);
assertThat(classPathArchives).hasSize(2);
assertThat(classPathArchives).hasSize(4);
assertThat(getUrls(classPathArchives)).containsOnly(
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/classes!/"),
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/foo.jar!/"));
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/foo.jar!/"),
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/bar.jar!/"),
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/baz.jar!/"));
for (Archive classPathArchive : classPathArchives) {
classPathArchive.close();
}
}
}
protected final URL[] getExpectedFileUrls(File explodedRoot) {
return getExpectedFiles(explodedRoot).stream().map(this::toUrl).toArray(URL[]::new);
}
protected final List<File> getExpectedFiles(File parent) {
List<File> expected = new ArrayList<>();
expected.add(new File(parent, "BOOT-INF/classes"));
expected.add(new File(parent, "BOOT-INF/lib/foo.jar"));
expected.add(new File(parent, "BOOT-INF/lib/bar.jar"));
expected.add(new File(parent, "BOOT-INF/lib/baz.jar"));
return expected;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@@ -42,29 +42,41 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests {
WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot, true));
List<Archive> archives = new ArrayList<>();
launcher.getClassPathArchivesIterator().forEachRemaining(archives::add);
assertThat(archives).hasSize(2);
assertThat(getUrls(archives)).containsOnly(new File(explodedRoot, "WEB-INF/classes").toURI().toURL(),
new File(explodedRoot, "WEB-INF/lib/foo.jar").toURI().toURL());
assertThat(getUrls(archives)).containsExactlyInAnyOrder(getExpectedFileUrls(explodedRoot));
for (Archive archive : archives) {
archive.close();
}
}
@Test
void archivedWarHasOnlyWebInfClassesAndContentsOWebInfLibOnClasspath() throws Exception {
void archivedWarHasOnlyWebInfClassesAndContentsOfWebInfLibOnClasspath() throws Exception {
File jarRoot = createJarArchive("archive.war", "WEB-INF");
try (JarFileArchive archive = new JarFileArchive(jarRoot)) {
WarLauncher launcher = new WarLauncher(archive);
List<Archive> classPathArchives = new ArrayList<>();
launcher.getClassPathArchivesIterator().forEachRemaining(classPathArchives::add);
assertThat(classPathArchives).hasSize(2);
assertThat(getUrls(classPathArchives)).containsOnly(
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/classes!/"),
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/foo.jar!/"));
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/foo.jar!/"),
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/bar.jar!/"),
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/baz.jar!/"));
for (Archive classPathArchive : classPathArchives) {
classPathArchive.close();
}
}
}
protected final URL[] getExpectedFileUrls(File explodedRoot) {
return getExpectedFiles(explodedRoot).stream().map(this::toUrl).toArray(URL[]::new);
}
protected final List<File> getExpectedFiles(File parent) {
List<File> expected = new ArrayList<>();
expected.add(new File(parent, "WEB-INF/classes"));
expected.add(new File(parent, "WEB-INF/lib/foo.jar"));
expected.add(new File(parent, "WEB-INF/lib/bar.jar"));
expected.add(new File(parent, "WEB-INF/lib/baz.jar"));
return expected;
}
}