Commit 0d207d43 authored by Andy Wilkinson's avatar Andy Wilkinson

Improve the performance of JarURLConnection

This commit improves the performance of JarURLConnection. There are two
main changes:

Firstly, the way in which the spec is determined has been changed so
that it’s no longer necessary to create an absolute file. Instead,
the JarFile’s pathFromRoot is used to extract the spec from the URL
relative to the JarFile.

Secondly, the number of temporary Objects that are created has been
reduced, for example by tracking an index as we process a String
rather than creating a new substring for each iteration.

See gh-6215
parent 9788a7bd
......@@ -199,6 +199,10 @@ final class AsciiBytes {
return false;
}
static String toString(byte[] bytes) {
return new String(bytes, UTF_8);
}
public static int hashCode(String string) {
// We're compatible with String's hashCode().
return string.hashCode();
......
......@@ -364,6 +364,10 @@ public class JarFile extends java.util.jar.JarFile {
this.entries.clearCache();
}
protected String getPathFromRoot() {
return this.pathFromRoot;
}
/**
* Register a {@literal 'java.protocol.handler.pkgs'} property so that a
* {@link URLStreamHandler} will be located to deal with jar URLs.
......
......@@ -17,22 +17,17 @@
package org.springframework.boot.loader.jar;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.net.URLStreamHandler;
import java.security.Permission;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* {@link java.net.JarURLConnection} used to support {@link JarFile#getUrl()}.
......@@ -68,16 +63,6 @@ class JarURLConnection extends java.net.JarURLConnection {
private static final String READ_ACTION = "read";
private static final Map<String, String> absoluteFileCache = Collections
.synchronizedMap(new LinkedHashMap<String, String>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
return size() >= 50;
}
});
private static ThreadLocal<Boolean> useFastExceptions = new ThreadLocal<Boolean>();
private final JarFile jarFile;
......@@ -94,32 +79,25 @@ class JarURLConnection extends java.net.JarURLConnection {
// What we pass to super is ultimately ignored
super(EMPTY_JAR_URL);
this.url = url;
String spec = getNormalizedFileUrl(url)
.substring(jarFile.getUrl().getFile().length());
String spec = extractFullSpec(url, jarFile.getPathFromRoot());
int separator;
while ((separator = spec.indexOf(SEPARATOR)) > 0) {
jarFile = getNestedJarFile(jarFile, spec.substring(0, separator));
spec = spec.substring(separator + SEPARATOR.length());
int index = 0;
while ((separator = spec.indexOf(SEPARATOR, index)) > 0) {
jarFile = getNestedJarFile(jarFile, spec.substring(index, separator));
index += separator + SEPARATOR.length();
}
this.jarFile = jarFile;
this.jarEntryName = getJarEntryName(spec);
this.jarEntryName = getJarEntryName(spec.substring(index));
}
private String getNormalizedFileUrl(URL url) {
private String extractFullSpec(URL url, String pathFromRoot) {
String file = url.getFile();
String path = "";
int separatorIndex = file.indexOf(SEPARATOR);
if (separatorIndex > 0) {
path = file.substring(separatorIndex);
file = file.substring(0, separatorIndex);
}
String absoluteFile = JarURLConnection.absoluteFileCache.get(file);
if (absoluteFile == null) {
absoluteFile = new File(URI.create(file).getSchemeSpecificPart())
.getAbsoluteFile().toURI().toString();
JarURLConnection.absoluteFileCache.put(file, absoluteFile);
if (separatorIndex < 0) {
return "";
}
return absoluteFile + path;
return file
.substring(separatorIndex + SEPARATOR.length() + pathFromRoot.length());
}
private JarFile getNestedJarFile(JarFile jarFile, String name) throws IOException {
......@@ -266,14 +244,13 @@ class JarURLConnection extends java.net.JarURLConnection {
}
private String decode(String source) {
int length = (source == null ? 0 : source.length());
if ((length == 0) || (source.indexOf('%') < 0)) {
return new AsciiBytes(source).toString();
if (source.length() == 0 || (source.indexOf('%') < 0)) {
return source;
}
ByteArrayOutputStream bos = new ByteArrayOutputStream(length);
ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length());
write(source, bos);
// AsciiBytes is what is used to store the JarEntries so make it symmetric
return new AsciiBytes(bos.toByteArray()).toString();
return AsciiBytes.toString(bos.toByteArray());
}
private void write(String source, ByteArrayOutputStream outputStream) {
......
......@@ -102,6 +102,28 @@ public class JarURLConnectionTests {
.hasSameContentAs(new ByteArrayInputStream(new byte[] { 3 }));
}
@Test
public void connectionToEntryUsingAbsoluteUrlForEntryFromNestedJarFile()
throws Exception {
URL absoluteUrl = new URL(
"jar:file:" + getAbsolutePath() + "!/nested.jar!/3.dat");
assertThat(new JarURLConnection(absoluteUrl,
this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")))
.getInputStream()).hasSameContentAs(
new ByteArrayInputStream(new byte[] { 3 }));
}
@Test
public void connectionToEntryUsingRelativeUrlForEntryFromNestedJarFile()
throws Exception {
URL absoluteUrl = new URL(
"jar:file:" + getRelativePath() + "!/nested.jar!/3.dat");
assertThat(new JarURLConnection(absoluteUrl,
this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar")))
.getInputStream()).hasSameContentAs(
new ByteArrayInputStream(new byte[] { 3 }));
}
private String getAbsolutePath() {
return this.rootJarFile.getAbsolutePath().replace('\\', '/');
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment