diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/launch/JarLauncher.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/launch/JarLauncher.java index ecabbc1fdf1..3a6d1339ca1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/launch/JarLauncher.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/launch/JarLauncher.java @@ -38,11 +38,7 @@ public class JarLauncher extends ExecutableArchiveLauncher { @Override protected boolean isIncludedOnClassPath(Archive.Entry entry) { - String name = entry.name(); - if (entry.isDirectory()) { - return name.equals("BOOT-INF/classes/"); - } - return name.startsWith("BOOT-INF/lib/"); + return isLibraryFileOrClassesDirectory(entry); } @Override @@ -50,6 +46,14 @@ public class JarLauncher extends ExecutableArchiveLauncher { return "BOOT-INF/"; } + static boolean isLibraryFileOrClassesDirectory(Archive.Entry entry) { + String name = entry.name(); + if (entry.isDirectory()) { + return name.equals("BOOT-INF/classes/"); + } + return name.startsWith("BOOT-INF/lib/"); + } + public static void main(String[] args) throws Exception { new JarLauncher().launch(args); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/launch/PropertiesLauncher.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/launch/PropertiesLauncher.java index 8b88484df48..efa9d80c0b3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/launch/PropertiesLauncher.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/launch/PropertiesLauncher.java @@ -140,7 +140,11 @@ public class PropertiesLauncher extends Launcher { private final Properties properties = new Properties(); public PropertiesLauncher() throws Exception { - this.archive = Archive.create(Launcher.class); + this(Archive.create(Launcher.class)); + } + + PropertiesLauncher(Archive archive) throws Exception { + this.archive = archive; this.homeDirectory = getHomeDirectory(); initializeProperties(); this.paths = getPaths(); @@ -464,6 +468,8 @@ public class PropertiesLauncher extends Launcher { path = cleanupPath(handleUrl(path)); urls.addAll(getClassPathUrlsForPath(path)); } + urls.addAll(getClassPathUrlsForRoot()); + debug.log("Using class path URLs %s", urls); return urls; } @@ -531,6 +537,11 @@ public class PropertiesLauncher extends Launcher { } } + private Set getClassPathUrlsForRoot() throws IOException { + debug.log("Adding classpath entries from root archive %s", this.archive); + return this.archive.getClassPathUrls(JarLauncher::isLibraryFileOrClassesDirectory); + } + private Predicate includeByPrefix(String prefix) { return (entry) -> (entry.isDirectory() && entry.name().equals(prefix)) || (isArchive(entry) && entry.name().startsWith(prefix)); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/launch/WarLauncher.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/launch/WarLauncher.java index a74e63c4abc..38318ba222c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/launch/WarLauncher.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/launch/WarLauncher.java @@ -37,11 +37,7 @@ public class WarLauncher extends ExecutableArchiveLauncher { @Override public boolean isIncludedOnClassPath(Archive.Entry entry) { - String name = entry.name(); - if (entry.isDirectory()) { - return name.equals("WEB-INF/classes/"); - } - return name.startsWith("WEB-INF/lib/") || name.startsWith("WEB-INF/lib-provided/"); + return isLibraryFileOrClassesDirectory(entry); } @Override @@ -49,6 +45,14 @@ public class WarLauncher extends ExecutableArchiveLauncher { return "WEB-INF/"; } + static boolean isLibraryFileOrClassesDirectory(Archive.Entry entry) { + String name = entry.name(); + if (entry.isDirectory()) { + return name.equals("WEB-INF/classes/"); + } + return name.startsWith("WEB-INF/lib/") || name.startsWith("WEB-INF/lib-provided/"); + } + public static void main(String[] args) throws Exception { new WarLauncher().launch(args); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/launch/PropertiesLauncherTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/launch/PropertiesLauncherTests.java index 6e41ed6f912..9bac00e9e74 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/launch/PropertiesLauncherTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/launch/PropertiesLauncherTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.loader.launch; import java.io.File; import java.io.FileOutputStream; +import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; import java.time.Duration; @@ -26,7 +27,10 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; import java.util.jar.Manifest; +import java.util.zip.ZipEntry; import org.assertj.core.api.Condition; import org.awaitility.Awaitility; @@ -309,8 +313,8 @@ class PropertiesLauncherTests { assertThat(Arrays.asList(this.launcher.getArgs("bar"))).hasToString("[foo, bar]"); } - @SuppressWarnings("unchecked") @Test + @SuppressWarnings("unchecked") void testLoadPathCustomizedUsingManifest() throws Exception { System.setProperty("loader.home", this.tempDir.getAbsolutePath()); Manifest manifest = new Manifest(); @@ -364,6 +368,29 @@ class PropertiesLauncherTests { assertThat(bytes).isNotEmpty(); } + @Test // gh-37992 + void classPathWithoutLoaderPathDefaultsToJarLauncherIncludes() throws Exception { + File file = new File(this.tempDir, "test.jar"); + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(file))) { + try (JarFile in = new JarFile(new File("src/test/resources/jars/app.jar"))) { + out.putNextEntry(new ZipEntry("BOOT-INF/")); + out.putNextEntry(new ZipEntry("BOOT-INF/classes/")); + out.putNextEntry(new ZipEntry("BOOT-INF/classes/demo/")); + out.putNextEntry(new ZipEntry("BOOT-INF/classes/demo/Application.class")); + try (InputStream classIn = in.getInputStream(in.getEntry("demo/Application.class"))) { + classIn.transferTo(out); + } + out.closeEntry(); + } + } + Archive archive = new JarFileArchive(file); + System.setProperty("loader.main", "demo.Application"); + this.launcher = new PropertiesLauncher(archive); + this.launcher.launch(new String[0]); + waitFor("Hello World"); + + } + private void waitFor(String value) { Awaitility.waitAtMost(Duration.ofSeconds(5)).until(this.output::toString, containsString(value)); }