Skip to content

Commit 8f5777c

Browse files
mbhavephilwebb
andcommitted
Optimize JarLauncher when used with exploded jar
- Previously, we would create a JarFileArchive for all nested jars. This was an additional overhead. We only need to create a JarFileArchive for jars that can have nested jars in them. For all other jars we only need the URL to build the classpath. - While iterating over nested entries in the exploded jar, we only need to look at BOOT-INF and we can skip any entry that does not match that. Closes gh-16655 Co-authored-by: Phillip Webb <[email protected]>
1 parent 58022d7 commit 8f5777c

File tree

13 files changed

+418
-128
lines changed

13 files changed

+418
-128
lines changed

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
package org.springframework.boot.loader;
1818

1919
import java.util.ArrayList;
20+
import java.util.Iterator;
2021
import java.util.List;
21-
import java.util.jar.JarEntry;
2222
import java.util.jar.Manifest;
2323

2424
import org.springframework.boot.loader.archive.Archive;
@@ -65,27 +65,64 @@ protected String getMainClass() throws Exception {
6565
}
6666

6767
@Override
68-
protected List<Archive> getClassPathArchives() throws Exception {
69-
List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));
70-
postProcessClassPathArchives(archives);
68+
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
69+
Iterator<Archive> archives = this.archive.getNestedArchives(this::isSearchCandidate, this::isNestedArchive);
70+
if (isPostProcessingClassPathArchives()) {
71+
archives = applyClassPathArchivePostProcessing(archives);
72+
}
7173
return archives;
7274
}
7375

76+
private Iterator<Archive> applyClassPathArchivePostProcessing(Iterator<Archive> archives) throws Exception {
77+
List<Archive> list = new ArrayList<Archive>();
78+
while (archives.hasNext()) {
79+
list.add(archives.next());
80+
}
81+
postProcessClassPathArchives(list);
82+
return list.iterator();
83+
}
84+
85+
/**
86+
* Determine if the specified entry is a a candidate for further searching.
87+
* @param entry the entry to check
88+
* @return {@code true} if the entry is a candidate for further searching
89+
*/
90+
protected boolean isSearchCandidate(Archive.Entry entry) {
91+
return true;
92+
}
93+
7494
/**
75-
* Determine if the specified {@link JarEntry} is a nested item that should be added
76-
* to the classpath. The method is called once for each entry.
77-
* @param entry the jar entry
95+
* Determine if the specified entry is a nested item that should be added to the
96+
* classpath.
97+
* @param entry the entry to check
7898
* @return {@code true} if the entry is a nested item (jar or folder)
7999
*/
80100
protected abstract boolean isNestedArchive(Archive.Entry entry);
81101

102+
/**
103+
* Return if post processing needs to be applied to the archives. For back
104+
* compatibility this method returns {@true}, but subclasses that don't override
105+
* {@link #postProcessClassPathArchives(List)} should provide an implementation that
106+
* returns {@code false}.
107+
* @return if the {@link #postProcessClassPathArchives(List)} method is implemented
108+
*/
109+
protected boolean isPostProcessingClassPathArchives() {
110+
return true;
111+
}
112+
82113
/**
83114
* Called to post-process archive entries before they are used. Implementations can
84115
* add and remove entries.
85116
* @param archives the archives
86117
* @throws Exception if the post processing fails
118+
* @see #isPostProcessingClassPathArchives()
87119
*/
88120
protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
89121
}
90122

123+
@Override
124+
protected boolean supportsNestedJars() {
125+
return this.archive.supportsNestedJars();
126+
}
127+
91128
}

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.loader;
1818

1919
import org.springframework.boot.loader.archive.Archive;
20+
import org.springframework.boot.loader.archive.Archive.EntryFilter;
2021

2122
/**
2223
* {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are
@@ -29,9 +30,12 @@
2930
*/
3031
public class JarLauncher extends ExecutableArchiveLauncher {
3132

32-
static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
33-
34-
static final String BOOT_INF_LIB = "BOOT-INF/lib/";
33+
static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
34+
if (entry.isDirectory()) {
35+
return entry.getName().equals("BOOT-INF/classes/");
36+
}
37+
return entry.getName().startsWith("BOOT-INF/lib/");
38+
};
3539

3640
public JarLauncher() {
3741
}
@@ -40,12 +44,19 @@ protected JarLauncher(Archive archive) {
4044
super(archive);
4145
}
4246

47+
@Override
48+
protected boolean isPostProcessingClassPathArchives() {
49+
return false;
50+
}
51+
52+
@Override
53+
protected boolean isSearchCandidate(Archive.Entry entry) {
54+
return entry.getName().startsWith("BOOT-INF/");
55+
}
56+
4357
@Override
4458
protected boolean isNestedArchive(Archive.Entry entry) {
45-
if (entry.isDirectory()) {
46-
return entry.getName().equals(BOOT_INF_CLASSES);
47-
}
48-
return entry.getName().startsWith(BOOT_INF_LIB);
59+
return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
4960
}
5061

5162
public static void main(String[] args) throws Exception {

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
import java.io.File;
2020
import java.net.URI;
2121
import java.net.URL;
22+
import java.net.URLClassLoader;
2223
import java.security.CodeSource;
2324
import java.security.ProtectionDomain;
2425
import java.util.ArrayList;
26+
import java.util.Iterator;
2527
import java.util.List;
2628

2729
import org.springframework.boot.loader.archive.Archive;
@@ -46,8 +48,10 @@ public abstract class Launcher {
4648
* @throws Exception if the application fails to launch
4749
*/
4850
protected void launch(String[] args) throws Exception {
49-
JarFile.registerUrlProtocolHandler();
50-
ClassLoader classLoader = createClassLoader(getClassPathArchives());
51+
if (supportsNestedJars()) {
52+
JarFile.registerUrlProtocolHandler();
53+
}
54+
ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
5155
launch(args, getMainClass(), classLoader);
5256
}
5357

@@ -56,11 +60,24 @@ protected void launch(String[] args) throws Exception {
5660
* @param archives the archives
5761
* @return the classloader
5862
* @throws Exception if the classloader cannot be created
63+
* @deprecated since 2.3.0 in favor of {@link #createClassLoader(Iterator)}
5964
*/
65+
@Deprecated
6066
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
61-
List<URL> urls = new ArrayList<>(archives.size());
62-
for (Archive archive : archives) {
63-
urls.add(archive.getUrl());
67+
return createClassLoader(archives.iterator());
68+
}
69+
70+
/**
71+
* Create a classloader for the specified archives.
72+
* @param archives the archives
73+
* @return the classloader
74+
* @throws Exception if the classloader cannot be created
75+
* @since 2.3.0
76+
*/
77+
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
78+
List<URL> urls = new ArrayList<>(50);
79+
while (archives.hasNext()) {
80+
urls.add(archives.next().getUrl());
6481
}
6582
return createClassLoader(urls.toArray(new URL[0]));
6683
}
@@ -72,7 +89,10 @@ protected ClassLoader createClassLoader(List<Archive> archives) throws Exception
7289
* @throws Exception if the classloader cannot be created
7390
*/
7491
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
75-
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
92+
if (supportsNestedJars()) {
93+
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
94+
}
95+
return new URLClassLoader(urls, getClass().getClassLoader());
7696
}
7797

7898
/**
@@ -109,8 +129,23 @@ protected MainMethodRunner createMainMethodRunner(String mainClass, String[] arg
109129
* Returns the archives that will be used to construct the class path.
110130
* @return the class path archives
111131
* @throws Exception if the class path archives cannot be obtained
132+
* @since 2.3.0
133+
*/
134+
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
135+
return getClassPathArchives().iterator();
136+
}
137+
138+
/**
139+
* Returns the archives that will be used to construct the class path.
140+
* @return the class path archives
141+
* @throws Exception if the class path archives cannot be obtained
142+
* @deprecated since 2.3.0 in favor of implementing
143+
* {@link #getClassPathArchivesIterator()}.
112144
*/
113-
protected abstract List<Archive> getClassPathArchives() throws Exception;
145+
@Deprecated
146+
protected List<Archive> getClassPathArchives() throws Exception {
147+
throw new IllegalStateException("Unexpected call to getClassPathArchives()");
148+
}
114149

115150
protected final Archive createArchive() throws Exception {
116151
ProtectionDomain protectionDomain = getClass().getProtectionDomain();
@@ -127,4 +162,14 @@ protected final Archive createArchive() throws Exception {
127162
return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
128163
}
129164

165+
/**
166+
* Returns if the launcher needs to support fully nested JARs. If this method returns
167+
* {@code false} then only regular JARs are supported and the additional URL and
168+
* ClassLoader support infrastructure will not be installed.
169+
* @return if nested JARs are supported
170+
*/
171+
protected boolean supportsNestedJars() {
172+
return true;
173+
}
174+
130175
}

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.net.URLDecoder;
2929
import java.util.ArrayList;
3030
import java.util.Collections;
31+
import java.util.Iterator;
3132
import java.util.LinkedHashSet;
3233
import java.util.List;
3334
import java.util.Locale;
@@ -447,12 +448,12 @@ private String getProperty(String propertyKey, String manifestKey, String defaul
447448
}
448449

449450
@Override
450-
protected List<Archive> getClassPathArchives() throws Exception {
451+
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
451452
List<Archive> lib = new ArrayList<>();
452453
for (String path : this.paths) {
453454
for (Archive archive : getClassPathArchives(path)) {
454455
if (archive instanceof ExplodedArchive) {
455-
List<Archive> nested = new ArrayList<>(archive.getNestedArchives(new ArchiveEntryFilter()));
456+
List<Archive> nested = asList(archive.getNestedArchives(null, new ArchiveEntryFilter()));
456457
nested.add(0, archive);
457458
lib.addAll(nested);
458459
}
@@ -462,7 +463,7 @@ protected List<Archive> getClassPathArchives() throws Exception {
462463
}
463464
}
464465
addNestedEntries(lib);
465-
return lib;
466+
return lib.iterator();
466467
}
467468

468469
private List<Archive> getClassPathArchives(String path) throws Exception {
@@ -543,7 +544,7 @@ private List<Archive> getNestedArchives(String path) throws Exception {
543544
root = "";
544545
}
545546
EntryFilter filter = new PrefixMatchingArchiveFilter(root);
546-
List<Archive> archives = new ArrayList<>(parent.getNestedArchives(filter));
547+
List<Archive> archives = asList(parent.getNestedArchives(null, filter));
547548
if (("".equals(root) || ".".equals(root)) && !path.endsWith(".jar") && parent != this.parent) {
548549
// You can't find the root with an entry filter so it has to be added
549550
// explicitly. But don't add the root of the parent archive.
@@ -557,12 +558,10 @@ private void addNestedEntries(List<Archive> lib) {
557558
// directories, meaning we are running from an executable JAR. We add nested
558559
// entries from there with low priority (i.e. at end).
559560
try {
560-
lib.addAll(this.parent.getNestedArchives((entry) -> {
561-
if (entry.isDirectory()) {
562-
return entry.getName().equals(JarLauncher.BOOT_INF_CLASSES);
563-
}
564-
return entry.getName().startsWith(JarLauncher.BOOT_INF_LIB);
565-
}));
561+
Iterator<Archive> archives = this.parent.getNestedArchives(null, JarLauncher.NESTED_ARCHIVE_ENTRY_FILTER);
562+
while (archives.hasNext()) {
563+
lib.add(archives.next());
564+
}
566565
}
567566
catch (IOException ex) {
568567
// Ignore
@@ -591,6 +590,14 @@ private String cleanupPath(String path) {
591590
return path;
592591
}
593592

593+
private List<Archive> asList(Iterator<Archive> iterator) {
594+
List<Archive> list = new ArrayList<Archive>();
595+
while (iterator.hasNext()) {
596+
list.add(iterator.next());
597+
}
598+
return list;
599+
}
600+
594601
public static void main(String[] args) throws Exception {
595602
PropertiesLauncher launcher = new PropertiesLauncher();
596603
args = launcher.getArgs(args);

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.loader;
1818

1919
import org.springframework.boot.loader.archive.Archive;
20+
import org.springframework.boot.loader.archive.Archive.Entry;
2021

2122
/**
2223
* {@link Launcher} for WAR based archives. This launcher for standard WAR archives.
@@ -29,29 +30,29 @@
2930
*/
3031
public class WarLauncher extends ExecutableArchiveLauncher {
3132

32-
private static final String WEB_INF = "WEB-INF/";
33-
34-
private static final String WEB_INF_CLASSES = WEB_INF + "classes/";
35-
36-
private static final String WEB_INF_LIB = WEB_INF + "lib/";
37-
38-
private static final String WEB_INF_LIB_PROVIDED = WEB_INF + "lib-provided/";
39-
4033
public WarLauncher() {
4134
}
4235

4336
protected WarLauncher(Archive archive) {
4437
super(archive);
4538
}
4639

40+
@Override
41+
protected boolean isPostProcessingClassPathArchives() {
42+
return false;
43+
}
44+
45+
@Override
46+
protected boolean isSearchCandidate(Entry entry) {
47+
return entry.getName().startsWith("WEB-INF/");
48+
}
49+
4750
@Override
4851
public boolean isNestedArchive(Archive.Entry entry) {
4952
if (entry.isDirectory()) {
50-
return entry.getName().equals(WEB_INF_CLASSES);
51-
}
52-
else {
53-
return entry.getName().startsWith(WEB_INF_LIB) || entry.getName().startsWith(WEB_INF_LIB_PROVIDED);
53+
return entry.getName().equals("WEB-INF/classes/");
5454
}
55+
return entry.getName().startsWith("WEB-INF/lib/") || entry.getName().startsWith("WEB-INF/lib-provided/");
5556
}
5657

5758
public static void main(String[] args) throws Exception {

0 commit comments

Comments
 (0)