diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ClassPathIndexFile.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ClassPathIndexFile.java index b095a85e512..ed11b19f01e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ClassPathIndexFile.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ClassPathIndexFile.java @@ -72,6 +72,13 @@ final class ClassPathIndexFile { return this.folders.contains(name); } + boolean containsEntry(String name) { + if (name == null || name.isEmpty()) { + return false; + } + return this.lines.contains(name); + } + List getUrls() { return Collections.unmodifiableList(this.lines.stream().map(this::asUrl).collect(Collectors.toList())); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java index 514c89b08bd..9ed5fee0aa7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java @@ -101,17 +101,18 @@ public abstract class ExecutableArchiveLauncher extends Launcher { @Override protected Iterator getClassPathArchivesIterator() throws Exception { - Archive.EntryFilter searchFilter = (entry) -> isSearchCandidate(entry) && !isFolderIndexed(entry); - Iterator archives = this.archive.getNestedArchives(searchFilter, this::isNestedArchive); + Archive.EntryFilter searchFilter = this::isSearchCandidate; + Iterator archives = this.archive.getNestedArchives(searchFilter, + (entry) -> isNestedArchive(entry) && !isEntryIndexed(entry)); if (isPostProcessingClassPathArchives()) { archives = applyClassPathArchivePostProcessing(archives); } return archives; } - private boolean isFolderIndexed(Archive.Entry entry) { + private boolean isEntryIndexed(Archive.Entry entry) { if (this.classPathIndex != null) { - return this.classPathIndex.containsFolder(entry.getName()); + return this.classPathIndex.containsEntry(entry.getName()); } return false; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/AbstractExecutableArchiveLauncherTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/AbstractExecutableArchiveLauncherTests.java index 13f0bdea98e..20069b8f912 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/AbstractExecutableArchiveLauncherTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/AbstractExecutableArchiveLauncherTests.java @@ -25,6 +25,7 @@ import java.io.Writer; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.List; @@ -52,11 +53,12 @@ public abstract class AbstractExecutableArchiveLauncherTests { File tempDir; protected File createJarArchive(String name, String entryPrefix) throws IOException { - return createJarArchive(name, entryPrefix, false); + return createJarArchive(name, entryPrefix, false, Collections.emptyList()); } @SuppressWarnings("resource") - protected File createJarArchive(String name, String entryPrefix, boolean indexed) throws IOException { + protected File createJarArchive(String name, String entryPrefix, boolean indexed, List extraLibs) + throws IOException { File archive = new File(this.tempDir, name); JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(archive)); jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/")); @@ -74,6 +76,9 @@ public abstract class AbstractExecutableArchiveLauncherTests { addNestedJars(entryPrefix, "/lib/foo.jar", jarOutputStream); addNestedJars(entryPrefix, "/lib/bar.jar", jarOutputStream); addNestedJars(entryPrefix, "/lib/baz.jar", jarOutputStream); + for (String lib : extraLibs) { + addNestedJars(entryPrefix, "/lib/" + lib, jarOutputStream); + } jarOutputStream.close(); return archive; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarLauncherTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarLauncherTests.java index c894a3480b2..0b265f3a3ef 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarLauncherTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarLauncherTests.java @@ -20,6 +20,8 @@ import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -72,7 +74,7 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests { @Test void explodedJarShouldPreserveClasspathOrderWhenIndexPresent() throws Exception { - File explodedRoot = explode(createJarArchive("archive.jar", "BOOT-INF", true)); + File explodedRoot = explode(createJarArchive("archive.jar", "BOOT-INF", true, Collections.emptyList())); JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true)); Iterator archives = launcher.getClassPathArchivesIterator(); URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives); @@ -80,6 +82,19 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests { assertThat(urls).containsExactly(getExpectedFileUrls(explodedRoot)); } + @Test + void jarFilesPresentInBootInfLibsAndNotInClasspathIndexShouldBeAddedAfterBootInfClasses() throws Exception { + ArrayList extraLibs = new ArrayList<>(Arrays.asList("extra-1.jar", "extra-2.jar")); + File explodedRoot = explode(createJarArchive("archive.jar", "BOOT-INF", true, extraLibs)); + JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true)); + Iterator archives = launcher.getClassPathArchivesIterator(); + URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives); + URL[] urls = classLoader.getURLs(); + List expectedFiles = getExpectedFilesWithExtraLibs(explodedRoot); + URL[] expectedFileUrls = expectedFiles.stream().map(this::toUrl).toArray(URL[]::new); + assertThat(urls).containsExactly(expectedFileUrls); + } + protected final URL[] getExpectedFileUrls(File explodedRoot) { return getExpectedFiles(explodedRoot).stream().map(this::toUrl).toArray(URL[]::new); } @@ -93,4 +108,15 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests { return expected; } + protected final List getExpectedFilesWithExtraLibs(File parent) { + List expected = new ArrayList<>(); + expected.add(new File(parent, "BOOT-INF/classes")); + expected.add(new File(parent, "BOOT-INF/lib/extra-1.jar")); + expected.add(new File(parent, "BOOT-INF/lib/extra-2.jar")); + 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; + } + }