Skip to content

Commit

Permalink
Optimize JarLauncher when used with exploded jar
Browse files Browse the repository at this point in the history
- 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 spring-projectsgh-16655

Co-authored-by: Phillip Webb <pwebb@pivotal.io>
  • Loading branch information
mbhave and philwebb committed Dec 12, 2019
1 parent 58022d7 commit 8f5777c
Show file tree
Hide file tree
Showing 13 changed files with 418 additions and 128 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
package org.springframework.boot.loader;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.Manifest;

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

@Override
protected List<Archive> getClassPathArchives() throws Exception {
List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));
postProcessClassPathArchives(archives);
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
Iterator<Archive> archives = this.archive.getNestedArchives(this::isSearchCandidate, this::isNestedArchive);
if (isPostProcessingClassPathArchives()) {
archives = applyClassPathArchivePostProcessing(archives);
}
return archives;
}

private Iterator<Archive> applyClassPathArchivePostProcessing(Iterator<Archive> archives) throws Exception {
List<Archive> list = new ArrayList<Archive>();
while (archives.hasNext()) {
list.add(archives.next());
}
postProcessClassPathArchives(list);
return list.iterator();
}

/**
* Determine if the specified entry is a a candidate for further searching.
* @param entry the entry to check
* @return {@code true} if the entry is a candidate for further searching
*/
protected boolean isSearchCandidate(Archive.Entry entry) {
return true;
}

/**
* Determine if the specified {@link JarEntry} is a nested item that should be added
* to the classpath. The method is called once for each entry.
* @param entry the jar entry
* Determine if the specified entry is a nested item that should be added to the
* classpath.
* @param entry the entry to check
* @return {@code true} if the entry is a nested item (jar or folder)
*/
protected abstract boolean isNestedArchive(Archive.Entry entry);

/**
* Return if post processing needs to be applied to the archives. For back
* compatibility this method returns {@true}, but subclasses that don't override
* {@link #postProcessClassPathArchives(List)} should provide an implementation that
* returns {@code false}.
* @return if the {@link #postProcessClassPathArchives(List)} method is implemented
*/
protected boolean isPostProcessingClassPathArchives() {
return true;
}

/**
* Called to post-process archive entries before they are used. Implementations can
* add and remove entries.
* @param archives the archives
* @throws Exception if the post processing fails
* @see #isPostProcessingClassPathArchives()
*/
protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
}

@Override
protected boolean supportsNestedJars() {
return this.archive.supportsNestedJars();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.springframework.boot.loader;

import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.EntryFilter;

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

static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";

static final String BOOT_INF_LIB = "BOOT-INF/lib/";
static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
if (entry.isDirectory()) {
return entry.getName().equals("BOOT-INF/classes/");
}
return entry.getName().startsWith("BOOT-INF/lib/");
};

public JarLauncher() {
}
Expand All @@ -40,12 +44,19 @@ protected JarLauncher(Archive archive) {
super(archive);
}

@Override
protected boolean isPostProcessingClassPathArchives() {
return false;
}

@Override
protected boolean isSearchCandidate(Archive.Entry entry) {
return entry.getName().startsWith("BOOT-INF/");
}

@Override
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
}

public static void main(String[] args) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.springframework.boot.loader.archive.Archive;
Expand All @@ -46,8 +48,10 @@ public abstract class Launcher {
* @throws Exception if the application fails to launch
*/
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
ClassLoader classLoader = createClassLoader(getClassPathArchives());
if (supportsNestedJars()) {
JarFile.registerUrlProtocolHandler();
}
ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
launch(args, getMainClass(), classLoader);
}

Expand All @@ -56,11 +60,24 @@ protected void launch(String[] args) throws Exception {
* @param archives the archives
* @return the classloader
* @throws Exception if the classloader cannot be created
* @deprecated since 2.3.0 in favor of {@link #createClassLoader(Iterator)}
*/
@Deprecated
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(archives.size());
for (Archive archive : archives) {
urls.add(archive.getUrl());
return createClassLoader(archives.iterator());
}

/**
* Create a classloader for the specified archives.
* @param archives the archives
* @return the classloader
* @throws Exception if the classloader cannot be created
* @since 2.3.0
*/
protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<>(50);
while (archives.hasNext()) {
urls.add(archives.next().getUrl());
}
return createClassLoader(urls.toArray(new URL[0]));
}
Expand All @@ -72,7 +89,10 @@ protected ClassLoader createClassLoader(List<Archive> archives) throws Exception
* @throws Exception if the classloader cannot be created
*/
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
if (supportsNestedJars()) {
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}
return new URLClassLoader(urls, getClass().getClassLoader());
}

/**
Expand Down Expand Up @@ -109,8 +129,23 @@ protected MainMethodRunner createMainMethodRunner(String mainClass, String[] arg
* Returns the archives that will be used to construct the class path.
* @return the class path archives
* @throws Exception if the class path archives cannot be obtained
* @since 2.3.0
*/
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
return getClassPathArchives().iterator();
}

/**
* Returns the archives that will be used to construct the class path.
* @return the class path archives
* @throws Exception if the class path archives cannot be obtained
* @deprecated since 2.3.0 in favor of implementing
* {@link #getClassPathArchivesIterator()}.
*/
protected abstract List<Archive> getClassPathArchives() throws Exception;
@Deprecated
protected List<Archive> getClassPathArchives() throws Exception {
throw new IllegalStateException("Unexpected call to getClassPathArchives()");
}

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

/**
* Returns if the launcher needs to support fully nested JARs. If this method returns
* {@code false} then only regular JARs are supported and the additional URL and
* ClassLoader support infrastructure will not be installed.
* @return if nested JARs are supported
*/
protected boolean supportsNestedJars() {
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -447,12 +448,12 @@ private String getProperty(String propertyKey, String manifestKey, String defaul
}

@Override
protected List<Archive> getClassPathArchives() throws Exception {
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
List<Archive> lib = new ArrayList<>();
for (String path : this.paths) {
for (Archive archive : getClassPathArchives(path)) {
if (archive instanceof ExplodedArchive) {
List<Archive> nested = new ArrayList<>(archive.getNestedArchives(new ArchiveEntryFilter()));
List<Archive> nested = asList(archive.getNestedArchives(null, new ArchiveEntryFilter()));
nested.add(0, archive);
lib.addAll(nested);
}
Expand All @@ -462,7 +463,7 @@ protected List<Archive> getClassPathArchives() throws Exception {
}
}
addNestedEntries(lib);
return lib;
return lib.iterator();
}

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

private List<Archive> asList(Iterator<Archive> iterator) {
List<Archive> list = new ArrayList<Archive>();
while (iterator.hasNext()) {
list.add(iterator.next());
}
return list;
}

public static void main(String[] args) throws Exception {
PropertiesLauncher launcher = new PropertiesLauncher();
args = launcher.getArgs(args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.springframework.boot.loader;

import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.Entry;

/**
* {@link Launcher} for WAR based archives. This launcher for standard WAR archives.
Expand All @@ -29,29 +30,29 @@
*/
public class WarLauncher extends ExecutableArchiveLauncher {

private static final String WEB_INF = "WEB-INF/";

private static final String WEB_INF_CLASSES = WEB_INF + "classes/";

private static final String WEB_INF_LIB = WEB_INF + "lib/";

private static final String WEB_INF_LIB_PROVIDED = WEB_INF + "lib-provided/";

public WarLauncher() {
}

protected WarLauncher(Archive archive) {
super(archive);
}

@Override
protected boolean isPostProcessingClassPathArchives() {
return false;
}

@Override
protected boolean isSearchCandidate(Entry entry) {
return entry.getName().startsWith("WEB-INF/");
}

@Override
public boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(WEB_INF_CLASSES);
}
else {
return entry.getName().startsWith(WEB_INF_LIB) || entry.getName().startsWith(WEB_INF_LIB_PROVIDED);
return entry.getName().equals("WEB-INF/classes/");
}
return entry.getName().startsWith("WEB-INF/lib/") || entry.getName().startsWith("WEB-INF/lib-provided/");
}

public static void main(String[] args) throws Exception {
Expand Down
Loading

0 comments on commit 8f5777c

Please sign in to comment.