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 <pwebb@pivotal.io>
pull/19364/head
Madhura Bhave 5 years ago
parent 58022d72f5
commit 8f5777cf9e

@ -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;
@ -65,27 +65,64 @@ public abstract class ExecutableArchiveLauncher extends Launcher {
}
@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();
}
}

@ -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
@ -29,9 +30,12 @@ import org.springframework.boot.loader.archive.Archive;
*/
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() {
}
@ -40,12 +44,19 @@ public class JarLauncher extends ExecutableArchiveLauncher {
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 {

@ -19,9 +19,11 @@ package org.springframework.boot.loader;
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;
@ -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);
}
@ -56,11 +60,24 @@ public abstract class Launcher {
* @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]));
}
@ -72,7 +89,10 @@ public abstract class Launcher {
* @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());
}
/**
@ -109,8 +129,23 @@ public abstract class Launcher {
* 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();
@ -127,4 +162,14 @@ public abstract class Launcher {
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;
}
}

@ -28,6 +28,7 @@ import java.net.URLConnection;
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;
@ -447,12 +448,12 @@ public class PropertiesLauncher extends Launcher {
}
@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);
}
@ -462,7 +463,7 @@ public class PropertiesLauncher extends Launcher {
}
}
addNestedEntries(lib);
return lib;
return lib.iterator();
}
private List<Archive> getClassPathArchives(String path) throws Exception {
@ -543,7 +544,7 @@ public class PropertiesLauncher extends Launcher {
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.
@ -557,12 +558,10 @@ public class PropertiesLauncher extends Launcher {
// 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
@ -591,6 +590,14 @@ public class PropertiesLauncher extends Launcher {
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);

@ -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.
@ -29,14 +30,6 @@ import org.springframework.boot.loader.archive.Archive;
*/
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() {
}
@ -44,14 +37,22 @@ public class WarLauncher extends ExecutableArchiveLauncher {
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 {

@ -19,6 +19,7 @@ package org.springframework.boot.loader.archive;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.jar.Manifest;
@ -47,13 +48,44 @@ public interface Archive extends Iterable<Archive.Entry>, AutoCloseable {
*/
Manifest getManifest() throws IOException;
/**
* Returns nested {@link Archive}s for entries that match the specified filters.
* @param searchFilter filter used to limit when additional sub-entry searching is
* required or {@code null} if all entries should be considered.
* @param includeFilter filter used to determine which entries should be included in
* the result or {@code null} if all entries should be included
* @return the nested archives
* @throws IOException on IO error
* @since 2.3.0
*/
default Iterator<Archive> getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter)
throws IOException {
EntryFilter combinedFilter = (entry) -> (searchFilter == null || searchFilter.matches(entry))
&& (includeFilter == null || includeFilter.matches(entry));
List<Archive> nestedArchives = getNestedArchives(combinedFilter);
return nestedArchives.iterator();
}
/**
* Returns nested {@link Archive}s for entries that match the specified filter.
* @param filter the filter used to limit entries
* @return nested archives
* @throws IOException if nested archives cannot be read
* @deprecated since 2.3.0 in favor of
* {@link #getNestedArchives(EntryFilter, EntryFilter)}
*/
List<Archive> getNestedArchives(EntryFilter filter) throws IOException;
@Deprecated
default List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
throw new IllegalStateException("Unexpected call to getNestedArchives(filter)");
}
@Deprecated
@Override
Iterator<Entry> iterator();
default boolean supportsNestedJars() {
return true;
}
/**
* Closes the {@code Archive}, releasing any open resources.
@ -87,6 +119,7 @@ public interface Archive extends Iterable<Archive.Entry>, AutoCloseable {
/**
* Strategy interface to filter {@link Entry Entries}.
*/
@FunctionalInterface
interface EntryFilter {
/**

@ -20,8 +20,8 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
@ -29,7 +29,6 @@ import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.jar.Manifest;
@ -39,6 +38,7 @@ import java.util.jar.Manifest;
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Madhura Bhave
* @since 1.0.0
*/
public class ExplodedArchive implements Archive {
@ -99,24 +99,23 @@ public class ExplodedArchive implements Archive {
}
@Override
public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
List<Archive> nestedArchives = new ArrayList<>();
for (Entry entry : this) {
if (filter.matches(entry)) {
nestedArchives.add(getNestedArchive(entry));
}
}
return Collections.unmodifiableList(nestedArchives);
public Iterator<Archive> getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) throws IOException {
return new ArchiveIterator(this.root, this.recursive, searchFilter, includeFilter);
}
@Override
public Iterator<Entry> iterator() {
return new FileEntryIterator(this.root, this.recursive);
return new EntryIterator(this.root, this.recursive, null, null);
}
protected Archive getNestedArchive(Entry entry) throws IOException {
File file = ((FileEntry) entry).getFile();
return (file.isDirectory() ? new ExplodedArchive(file) : new JarFileArchive(file));
return (file.isDirectory() ? new ExplodedArchive(file) : new SimpleJarFileArchive((FileEntry) entry));
}
@Override
public boolean supportsNestedJars() {
return false;
}
@Override
@ -132,21 +131,30 @@ public class ExplodedArchive implements Archive {
/**
* File based {@link Entry} {@link Iterator}.
*/
private static class FileEntryIterator implements Iterator<Entry> {
private abstract static class AbstractIterator<T> implements Iterator<T> {
private final Comparator<File> entryComparator = new EntryComparator();
private static final Comparator<File> entryComparator = Comparator.comparing(File::getAbsolutePath);
private final File root;
private final boolean recursive;
private final EntryFilter searchFilter;
private final EntryFilter includeFilter;
private final Deque<Iterator<File>> stack = new LinkedList<>();
private File current;
private FileEntry current;
private String rootUrl;
FileEntryIterator(File root, boolean recursive) {
AbstractIterator(File root, boolean recursive, EntryFilter searchFilter, EntryFilter includeFilter) {
this.root = root;
this.rootUrl = this.root.toURI().getPath();
this.recursive = recursive;
this.searchFilter = searchFilter;
this.includeFilter = includeFilter;
this.stack.add(listFiles(root));
this.current = poll();
}
@ -157,34 +165,28 @@ public class ExplodedArchive implements Archive {
}
@Override
public Entry next() {
if (this.current == null) {
public T next() {
FileEntry entry = this.current;
if (entry == null) {
throw new NoSuchElementException();
}
File file = this.current;
if (file.isDirectory() && (this.recursive || file.getParentFile().equals(this.root))) {
this.stack.addFirst(listFiles(file));
}
this.current = poll();
String name = file.toURI().getPath().substring(this.root.toURI().getPath().length());
return new FileEntry(name, file);
return adapt(entry);
}
private Iterator<File> listFiles(File file) {
File[] files = file.listFiles();
if (files == null) {
return Collections.emptyIterator();
}
Arrays.sort(files, this.entryComparator);
return Arrays.asList(files).iterator();
}
private File poll() {
private FileEntry poll() {
while (!this.stack.isEmpty()) {
while (this.stack.peek().hasNext()) {
File file = this.stack.peek().next();
if (!SKIPPED_NAMES.contains(file.getName())) {
return file;
if (SKIPPED_NAMES.contains(file.getName())) {
continue;
}
FileEntry entry = getFileEntry(file);
if (isListable(entry)) {
this.stack.addFirst(listFiles(file));
}
if (this.includeFilter == null || this.includeFilter.matches(entry)) {
return entry;
}
}
this.stack.poll();
@ -192,21 +194,64 @@ public class ExplodedArchive implements Archive {
return null;
}
private FileEntry getFileEntry(File file) {
URI uri = file.toURI();
String name = uri.getPath().substring(this.rootUrl.length());
try {
return new FileEntry(name, file, uri.toURL());
}
catch (MalformedURLException ex) {
throw new IllegalStateException(ex);
}
}
private boolean isListable(FileEntry entry) {
return entry.isDirectory() && (this.recursive || entry.getFile().getParentFile().equals(this.root))
&& (this.searchFilter == null || this.searchFilter.matches(entry))
&& (this.includeFilter == null || !this.includeFilter.matches(entry));
}
private Iterator<File> listFiles(File file) {
File[] files = file.listFiles();
if (files == null) {
return Collections.emptyIterator();
}
Arrays.sort(files, entryComparator);
return Arrays.asList(files).iterator();
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
/**
* {@link Comparator} that orders {@link File} entries by their absolute paths.
*/
private static class EntryComparator implements Comparator<File> {
protected abstract T adapt(FileEntry entry);
@Override
public int compare(File o1, File o2) {
return o1.getAbsolutePath().compareTo(o2.getAbsolutePath());
}
}
private static class EntryIterator extends AbstractIterator<Entry> {
EntryIterator(File root, boolean recursive, EntryFilter searchFilter, EntryFilter includeFilter) {
super(root, recursive, searchFilter, includeFilter);
}
@Override
protected Entry adapt(FileEntry entry) {
return entry;
}
}
private static class ArchiveIterator extends AbstractIterator<Archive> {
ArchiveIterator(File root, boolean recursive, EntryFilter searchFilter, EntryFilter includeFilter) {
super(root, recursive, searchFilter, includeFilter);
}
@Override
protected Archive adapt(FileEntry entry) {
File file = entry.getFile();
return (file.isDirectory() ? new ExplodedArchive(file) : new SimpleJarFileArchive(entry));
}
}
@ -220,9 +265,12 @@ public class ExplodedArchive implements Archive {
private final File file;
FileEntry(String name, File file) {
private final URL url;
FileEntry(String name, File file, URL url) {
this.name = name;
this.file = file;
this.url = url;
}
File getFile() {
@ -239,6 +287,55 @@ public class ExplodedArchive implements Archive {
return this.name;
}
URL getUrl() {
return this.url;
}
}
/**
* {@link Archive} implementation backed by a simple JAR file that doesn't itself
* contain nested archives.
*/
private static class SimpleJarFileArchive implements Archive {
private final URL url;
SimpleJarFileArchive(FileEntry file) {
this.url = file.getUrl();
}
@Override
public URL getUrl() throws MalformedURLException {
return this.url;
}
@Override
public Manifest getManifest() throws IOException {
return null;
}
@Override
public Iterator<Archive> getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter)
throws IOException {
return Collections.emptyIterator();
}
@Override
public Iterator<Entry> iterator() {
return Collections.emptyIterator();
}
@Override
public String toString() {
try {
return getUrl().toString();
}
catch (Exception ex) {
return "jar archive";
}
}
}
}

@ -23,11 +23,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.jar.JarEntry;
import java.util.jar.Manifest;
@ -80,19 +76,13 @@ public class JarFileArchive implements Archive {
}
@Override
public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
List<Archive> nestedArchives = new ArrayList<>();
for (Entry entry : this) {
if (filter.matches(entry)) {
nestedArchives.add(getNestedArchive(entry));
}
}
return Collections.unmodifiableList(nestedArchives);
public Iterator<Archive> getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) throws IOException {
return new NestedArchiveIterator(this.jarFile.iterator(), searchFilter, includeFilter);
}
@Override
public Iterator<Entry> iterator() {
return new EntryIterator(this.jarFile.entries());
return new EntryIterator(this.jarFile.iterator(), null, null);
}
@Override
@ -169,29 +159,85 @@ public class JarFileArchive implements Archive {
}
/**
* {@link Archive.Entry} iterator implementation backed by {@link JarEntry}.
* Abstract base class for iterator implementations.
*/
private static class EntryIterator implements Iterator<Entry> {
private abstract static class AbstractIterator<T> implements Iterator<T> {
private final Iterator<JarEntry> iterator;
private final Enumeration<JarEntry> enumeration;
private final EntryFilter searchFilter;
EntryIterator(Enumeration<JarEntry> enumeration) {
this.enumeration = enumeration;
private final EntryFilter includeFilter;
private Entry current;
AbstractIterator(Iterator<JarEntry> iterator, EntryFilter searchFilter, EntryFilter includeFilter) {
this.iterator = iterator;
this.searchFilter = searchFilter;
this.includeFilter = includeFilter;
this.current = poll();
}
@Override
public boolean hasNext() {
return this.enumeration.hasMoreElements();
return this.current != null;
}
@Override
public T next() {
T result = adapt(this.current);
this.current = poll();
return result;
}
private Entry poll() {
while (this.iterator.hasNext()) {
JarFileEntry candidate = new JarFileEntry(this.iterator.next());
if ((this.searchFilter == null || this.searchFilter.matches(candidate))
&& (this.includeFilter == null || this.includeFilter.matches(candidate))) {
return candidate;
}
}
return null;
}
protected abstract T adapt(Entry entry);
}
/**
* {@link Archive.Entry} iterator implementation backed by {@link JarEntry}.
*/
private static class EntryIterator extends AbstractIterator<Entry> {
EntryIterator(Iterator<JarEntry> iterator, EntryFilter searchFilter, EntryFilter includeFilter) {
super(iterator, searchFilter, includeFilter);
}
@Override
public Entry next() {
return new JarFileEntry(this.enumeration.nextElement());
protected Entry adapt(Entry entry) {
return entry;
}
}
/**
* Nested {@link Archive} iterator implementation backed by {@link JarEntry}.
*/
private class NestedArchiveIterator extends AbstractIterator<Archive> {
NestedArchiveIterator(Iterator<JarEntry> iterator, EntryFilter searchFilter, EntryFilter includeFilter) {
super(iterator, searchFilter, includeFilter);
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
protected Archive adapt(Entry entry) {
try {
return getNestedArchive(entry);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}

@ -48,7 +48,7 @@ import org.springframework.boot.loader.data.RandomAccessDataFile;
* @author Andy Wilkinson
* @since 1.0.0
*/
public class JarFile extends java.util.jar.JarFile {
public class JarFile extends java.util.jar.JarFile implements Iterable<java.util.jar.JarEntry> {
private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
@ -191,7 +191,7 @@ public class JarFile extends java.util.jar.JarFile {
@Override
public Enumeration<java.util.jar.JarEntry> entries() {
final Iterator<JarEntry> iterator = this.entries.iterator();
Iterator<java.util.jar.JarEntry> iterator = iterator();
return new Enumeration<java.util.jar.JarEntry>() {
@Override
@ -207,6 +207,17 @@ public class JarFile extends java.util.jar.JarFile {
};
}
/**
* Return an iterator for the contained entries.
* @see java.lang.Iterable#iterator()
* @since 2.3.0
*/
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Iterator<java.util.jar.JarEntry> iterator() {
return (Iterator) this.entries.iterator();
}
public JarEntry getJarEntry(CharSequence name) {
return this.entries.getEntry(name);
}

@ -20,6 +20,7 @@ import java.io.File;
import java.net.URL;
import java.util.List;
import org.codehaus.plexus.util.CollectionUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.loader.archive.Archive;
@ -39,7 +40,7 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests {
void explodedJarHasOnlyBootInfClassesAndContentsOfBootInfLibOnClasspath() throws Exception {
File explodedRoot = explode(createJarArchive("archive.jar", "BOOT-INF"));
JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true));
List<Archive> archives = launcher.getClassPathArchives();
List<Archive> archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator());
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());
@ -53,7 +54,7 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests {
File jarRoot = createJarArchive("archive.jar", "BOOT-INF");
try (JarFileArchive archive = new JarFileArchive(jarRoot)) {
JarLauncher launcher = new JarLauncher(archive);
List<Archive> classPathArchives = launcher.getClassPathArchives();
List<Archive> classPathArchives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator());
assertThat(classPathArchives).hasSize(2);
assertThat(getUrls(classPathArchives)).containsOnly(
new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/classes!/"),

@ -30,6 +30,7 @@ import java.util.jar.Manifest;
import org.assertj.core.api.Condition;
import org.awaitility.Awaitility;
import org.codehaus.plexus.util.CollectionUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -139,7 +140,7 @@ class PropertiesLauncherTests {
System.setProperty("loader.path", "jars/");
PropertiesLauncher launcher = new PropertiesLauncher();
assertThat(ReflectionTestUtils.getField(launcher, "paths").toString()).isEqualTo("[jars/]");
List<Archive> archives = launcher.getClassPathArchives();
List<Archive> archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator());
assertThat(archives).areExactly(1, endingWith("app.jar"));
}
@ -169,7 +170,7 @@ class PropertiesLauncherTests {
PropertiesLauncher launcher = new PropertiesLauncher();
assertThat(ReflectionTestUtils.getField(launcher, "paths").toString())
.isEqualTo("[jar:file:./src/test/resources/nested-jars/app.jar!/]");
List<Archive> archives = launcher.getClassPathArchives();
List<Archive> archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator());
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
assertThat(archives).areExactly(1, endingWith("app.jar"));
}
@ -178,7 +179,7 @@ class PropertiesLauncherTests {
void testUserSpecifiedRootOfJarPathWithDot() throws Exception {
System.setProperty("loader.path", "nested-jars/app.jar!/./");
PropertiesLauncher launcher = new PropertiesLauncher();
List<Archive> archives = launcher.getClassPathArchives();
List<Archive> archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator());
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
assertThat(archives).areExactly(1, endingWith("app.jar"));
}
@ -187,7 +188,7 @@ class PropertiesLauncherTests {
void testUserSpecifiedRootOfJarPathWithDotAndJarPrefix() throws Exception {
System.setProperty("loader.path", "jar:file:./src/test/resources/nested-jars/app.jar!/./");
PropertiesLauncher launcher = new PropertiesLauncher();
List<Archive> archives = launcher.getClassPathArchives();
List<Archive> archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator());
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
}
@ -196,7 +197,7 @@ class PropertiesLauncherTests {
System.setProperty("loader.path", "nested-jars/app.jar");
System.setProperty("loader.main", "demo.Application");
PropertiesLauncher launcher = new PropertiesLauncher();
List<Archive> archives = launcher.getClassPathArchives();
List<Archive> archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator());
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
assertThat(archives).areExactly(1, endingWith("app.jar"));
}
@ -206,7 +207,7 @@ class PropertiesLauncherTests {
System.setProperty("loader.path", "nested-jars/app.jar!/foo.jar");
System.setProperty("loader.main", "demo.Application");
PropertiesLauncher launcher = new PropertiesLauncher();
List<Archive> archives = launcher.getClassPathArchives();
List<Archive> archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator());
assertThat(archives).hasSize(1).areExactly(1, endingWith("foo.jar!/"));
}
@ -335,7 +336,7 @@ class PropertiesLauncherTests {
loaderPath.mkdir();
System.setProperty("loader.path", loaderPath.toURI().toURL().toString());
PropertiesLauncher launcher = new PropertiesLauncher();
List<Archive> archives = launcher.getClassPathArchives();
List<Archive> archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator());
assertThat(archives.size()).isEqualTo(1);
File archiveRoot = (File) ReflectionTestUtils.getField(archives.get(0), "root");
assertThat(archiveRoot).isEqualTo(loaderPath);

@ -20,6 +20,7 @@ import java.io.File;
import java.net.URL;
import java.util.List;
import org.codehaus.plexus.util.CollectionUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.loader.archive.Archive;
@ -39,7 +40,7 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests {
void explodedWarHasOnlyWebInfClassesAndContentsOfWebInfLibOnClasspath() throws Exception {
File explodedRoot = explode(createJarArchive("archive.war", "WEB-INF"));
WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot, true));
List<Archive> archives = launcher.getClassPathArchives();
List<Archive> archives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator());
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());
@ -53,7 +54,7 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests {
File jarRoot = createJarArchive("archive.war", "WEB-INF");
try (JarFileArchive archive = new JarFileArchive(jarRoot)) {
WarLauncher launcher = new WarLauncher(archive);
List<Archive> classPathArchives = launcher.getClassPathArchives();
List<Archive> classPathArchives = CollectionUtils.iteratorToList(launcher.getClassPathArchivesIterator());
assertThat(classPathArchives).hasSize(2);
assertThat(getUrls(classPathArchives)).containsOnly(
new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/classes!/"),

@ -74,7 +74,6 @@ class ExplodedArchiveTests {
private void createArchive(String folderName) throws Exception {
File file = new File(this.tempDir, "test.jar");
TestJarCreator.createTestJar(file);
this.rootFolder = (StringUtils.hasText(folderName) ? new File(this.tempDir, folderName)
: new File(this.tempDir, UUID.randomUUID().toString()));
JarFile jarFile = new JarFile(file);
@ -102,7 +101,7 @@ class ExplodedArchiveTests {
@Test
void getEntries() {
Map<String, Archive.Entry> entries = getEntriesMap(this.archive);
assertThat(entries.size()).isEqualTo(12);
assertThat(entries).hasSize(12);
}
@Test
@ -121,7 +120,7 @@ class ExplodedArchiveTests {
Entry entry = getEntriesMap(this.archive).get("nested.jar");
Archive nested = this.archive.getNestedArchive(entry);
assertThat(nested.getUrl().toString()).isEqualTo(this.rootFolder.toURI() + "nested.jar");
((JarFileArchive) nested).close();
nested.close();
}
@Test

Loading…
Cancel
Save