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>
This commit is contained in:
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 {@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 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 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 abstract List<Archive> getClassPathArchives() throws Exception;
|
||||
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()}.
|
||||
*/
|
||||
@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;
|
||||
|
||||
FileEntryIterator(File root, boolean recursive) {
|
||||
private String rootUrl;
|
||||
|
||||
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 Enumeration<JarEntry> enumeration;
|
||||
private final Iterator<JarEntry> iterator;
|
||||
|
||||
EntryIterator(Enumeration<JarEntry> enumeration) {
|
||||
this.enumeration = enumeration;
|
||||
private final EntryFilter searchFilter;
|
||||
|
||||
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 Entry next() {
|
||||
return new JarFileEntry(this.enumeration.nextElement());
|
||||
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 void remove() {
|
||||
throw new UnsupportedOperationException("remove");
|
||||
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
|
||||
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…
Reference in New Issue