commit
194fe4b644
|
@ -183,7 +183,6 @@ public class Handler extends URLStreamHandler {
|
||||||
* Clear any internal caches.
|
* Clear any internal caches.
|
||||||
*/
|
*/
|
||||||
public static void clearCache() {
|
public static void clearCache() {
|
||||||
JarFileUrlKey.clearCache();
|
|
||||||
JarUrlConnection.clearCache();
|
JarUrlConnection.clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,68 +16,56 @@
|
||||||
|
|
||||||
package org.springframework.boot.loader.net.protocol.jar;
|
package org.springframework.boot.loader.net.protocol.jar;
|
||||||
|
|
||||||
import java.lang.ref.SoftReference;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Locale;
|
import java.util.Objects;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to generate a string key from a jar file {@link URL} that can be used as a
|
* A fast cache key for a jar file {@link URL} that doesn't trigger DNS lookups.
|
||||||
* cache key.
|
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
final class JarFileUrlKey {
|
final class JarFileUrlKey {
|
||||||
|
|
||||||
private static volatile SoftReference<Map<URL, String>> cache;
|
private final String protocol;
|
||||||
|
|
||||||
private JarFileUrlKey() {
|
private final String host;
|
||||||
|
|
||||||
|
private final int port;
|
||||||
|
|
||||||
|
private final String file;
|
||||||
|
|
||||||
|
private final boolean runtimeRef;
|
||||||
|
|
||||||
|
JarFileUrlKey(URL url) {
|
||||||
|
this.protocol = url.getProtocol();
|
||||||
|
this.host = url.getHost();
|
||||||
|
this.port = (url.getPort() != -1) ? url.getPort() : url.getDefaultPort();
|
||||||
|
this.file = url.getFile();
|
||||||
|
this.runtimeRef = "runtime".equals(url.getRef());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Get the {@link JarFileUrlKey} for the given URL.
|
public int hashCode() {
|
||||||
* @param url the source URL
|
return Objects.hashCode(this.file);
|
||||||
* @return a {@link JarFileUrlKey} instance
|
|
||||||
*/
|
|
||||||
static String get(URL url) {
|
|
||||||
if (!isCachableUrl(url)) {
|
|
||||||
return create(url);
|
|
||||||
}
|
|
||||||
Map<URL, String> cache = (JarFileUrlKey.cache != null) ? JarFileUrlKey.cache.get() : null;
|
|
||||||
if (cache == null) {
|
|
||||||
cache = new ConcurrentHashMap<>();
|
|
||||||
JarFileUrlKey.cache = new SoftReference<>(cache);
|
|
||||||
}
|
|
||||||
return cache.computeIfAbsent(url, JarFileUrlKey::create);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isCachableUrl(URL url) {
|
@Override
|
||||||
// Don't cache URL that have a host since equals() will perform DNS lookup
|
public boolean equals(Object obj) {
|
||||||
return url.getHost() == null || url.getHost().isEmpty();
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
JarFileUrlKey other = (JarFileUrlKey) obj;
|
||||||
|
// We check file first as case sensitive and the most likely item to be different
|
||||||
|
return Objects.equals(this.file, other.file) && equalsIgnoringCase(this.protocol, other.protocol)
|
||||||
|
&& equalsIgnoringCase(this.host, other.host) && (this.port == other.port)
|
||||||
|
&& (this.runtimeRef == other.runtimeRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String create(URL url) {
|
private boolean equalsIgnoringCase(String s1, String s2) {
|
||||||
StringBuilder value = new StringBuilder();
|
return (s1 == s2) || (s1 != null && s1.equalsIgnoreCase(s2));
|
||||||
String protocol = url.getProtocol();
|
|
||||||
String host = url.getHost();
|
|
||||||
int port = (url.getPort() != -1) ? url.getPort() : url.getDefaultPort();
|
|
||||||
String file = url.getFile();
|
|
||||||
value.append(protocol.toLowerCase(Locale.ROOT));
|
|
||||||
value.append(":");
|
|
||||||
if (host != null && !host.isEmpty()) {
|
|
||||||
value.append(host.toLowerCase(Locale.ROOT));
|
|
||||||
value.append((port != -1) ? ":" + port : "");
|
|
||||||
}
|
|
||||||
value.append((file != null) ? file : "");
|
|
||||||
if ("runtime".equals(url.getRef())) {
|
|
||||||
value.append("#runtime");
|
|
||||||
}
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void clearCache() {
|
|
||||||
cache = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,7 +144,7 @@ class UrlJarFiles {
|
||||||
*/
|
*/
|
||||||
private static final class Cache {
|
private static final class Cache {
|
||||||
|
|
||||||
private final Map<String, JarFile> jarFileUrlToJarFile = new HashMap<>();
|
private final Map<JarFileUrlKey, JarFile> jarFileUrlToJarFile = new HashMap<>();
|
||||||
|
|
||||||
private final Map<JarFile, URL> jarFileToJarFileUrl = new HashMap<>();
|
private final Map<JarFile, URL> jarFileToJarFileUrl = new HashMap<>();
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ class UrlJarFiles {
|
||||||
* @return the cached {@link JarFile} or {@code null}
|
* @return the cached {@link JarFile} or {@code null}
|
||||||
*/
|
*/
|
||||||
JarFile get(URL jarFileUrl) {
|
JarFile get(URL jarFileUrl) {
|
||||||
String urlKey = JarFileUrlKey.get(jarFileUrl);
|
JarFileUrlKey urlKey = new JarFileUrlKey(jarFileUrl);
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
return this.jarFileUrlToJarFile.get(urlKey);
|
return this.jarFileUrlToJarFile.get(urlKey);
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,7 @@ class UrlJarFiles {
|
||||||
* they were already there
|
* they were already there
|
||||||
*/
|
*/
|
||||||
boolean putIfAbsent(URL jarFileUrl, JarFile jarFile) {
|
boolean putIfAbsent(URL jarFileUrl, JarFile jarFile) {
|
||||||
String urlKey = JarFileUrlKey.get(jarFileUrl);
|
JarFileUrlKey urlKey = new JarFileUrlKey(jarFileUrl);
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
JarFile cached = this.jarFileUrlToJarFile.get(urlKey);
|
JarFile cached = this.jarFileUrlToJarFile.get(urlKey);
|
||||||
if (cached == null) {
|
if (cached == null) {
|
||||||
|
@ -200,7 +200,7 @@ class UrlJarFiles {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
URL removedUrl = this.jarFileToJarFileUrl.remove(jarFile);
|
URL removedUrl = this.jarFileToJarFileUrl.remove(jarFile);
|
||||||
if (removedUrl != null) {
|
if (removedUrl != null) {
|
||||||
this.jarFileUrlToJarFile.remove(JarFileUrlKey.get(removedUrl));
|
this.jarFileUrlToJarFile.remove(new JarFileUrlKey(removedUrl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.boot.loader.net.protocol.jar;
|
package org.springframework.boot.loader.net.protocol.jar;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
@ -38,51 +39,66 @@ class JarFileUrlKeyTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getCreatesKey() throws Exception {
|
void equalsAndHashCode() throws Exception {
|
||||||
URL url = new URL("jar:nested:/my.jar/!mynested.jar!/my/path");
|
JarFileUrlKey k1 = key("jar:nested:/my.jar/!mynested.jar!/my/path");
|
||||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("jar:nested:/my.jar/!mynested.jar!/my/path");
|
JarFileUrlKey k2 = key("jar:nested:/my.jar/!mynested.jar!/my/path");
|
||||||
|
JarFileUrlKey k3 = key("jar:nested:/my.jar/!mynested.jar!/my/path2");
|
||||||
|
assertThat(k1.hashCode()).isEqualTo(k2.hashCode())
|
||||||
|
.isEqualTo("nested:/my.jar/!mynested.jar!/my/path".hashCode());
|
||||||
|
assertThat(k1).isEqualTo(k1).isEqualTo(k2).isNotEqualTo(k3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getWhenUppercaseProtocolCreatesKey() throws Exception {
|
void equalsWhenUppercaseAndLowercaseProtocol() throws Exception {
|
||||||
URL url = new URL("JAR:nested:/my.jar/!mynested.jar!/my/path");
|
JarFileUrlKey k1 = key("JAR:nested:/my.jar/!mynested.jar!/my/path");
|
||||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("jar:nested:/my.jar/!mynested.jar!/my/path");
|
JarFileUrlKey k2 = key("jar:nested:/my.jar/!mynested.jar!/my/path");
|
||||||
|
assertThat(k1).isEqualTo(k2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getWhenHasHostAndPortCreatesKey() throws Exception {
|
void equalsWhenHasHostAndPort() throws Exception {
|
||||||
URL url = new URL("https://example.com:1234/test");
|
JarFileUrlKey k1 = key("https://example.com:1234/test");
|
||||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("https:example.com:1234/test");
|
JarFileUrlKey k2 = key("https://example.com:1234/test");
|
||||||
|
assertThat(k1).isEqualTo(k2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getWhenHasUppercaseHostCreatesKey() throws Exception {
|
void equalsWhenHasUppercaseAndLowercaseHost() throws Exception {
|
||||||
URL url = new URL("https://EXAMPLE.com:1234/test");
|
JarFileUrlKey k1 = key("https://EXAMPLE.com:1234/test");
|
||||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("https:example.com:1234/test");
|
JarFileUrlKey k2 = key("https://example.com:1234/test");
|
||||||
|
assertThat(k1).isEqualTo(k2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getWhenHasNoPortCreatesKeyWithDefaultPort() throws Exception {
|
void equalsWhenHasNoPortUsesDefaultPort() throws Exception {
|
||||||
URL url = new URL("https://EXAMPLE.com/test");
|
JarFileUrlKey k1 = key("https://EXAMPLE.com/test");
|
||||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("https:example.com:443/test");
|
JarFileUrlKey k2 = key("https://example.com:443/test");
|
||||||
|
assertThat(k1).isEqualTo(k2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getWhenHasNoFileCreatesKey() throws Exception {
|
void equalsWhenHasNoFile() throws Exception {
|
||||||
URL url = new URL("https://EXAMPLE.com");
|
JarFileUrlKey k1 = key("https://EXAMPLE.com");
|
||||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("https:example.com:443");
|
JarFileUrlKey k2 = key("https://example.com:443");
|
||||||
|
assertThat(k1).isEqualTo(k2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getWhenHasRuntimeRefCreatesKey() throws Exception {
|
void equalsWhenHasRuntimeRef() throws Exception {
|
||||||
URL url = new URL("jar:nested:/my.jar/!mynested.jar!/my/path#runtime");
|
JarFileUrlKey k1 = key("jar:nested:/my.jar/!mynested.jar!/my/path#runtime");
|
||||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("jar:nested:/my.jar/!mynested.jar!/my/path#runtime");
|
JarFileUrlKey k2 = key("jar:nested:/my.jar/!mynested.jar!/my/path#runtime");
|
||||||
|
assertThat(k1).isEqualTo(k2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getWhenHasOtherRefCreatesKeyWithoutRef() throws Exception {
|
void equalsWhenHasOtherRefIgnoresRefs() throws Exception {
|
||||||
URL url = new URL("jar:nested:/my.jar/!mynested.jar!/my/path#example");
|
JarFileUrlKey k1 = key("jar:nested:/my.jar/!mynested.jar!/my/path#example");
|
||||||
assertThat(JarFileUrlKey.get(url)).isEqualTo("jar:nested:/my.jar/!mynested.jar!/my/path");
|
JarFileUrlKey k2 = key("jar:nested:/my.jar/!mynested.jar!/my/path");
|
||||||
|
assertThat(k1).isEqualTo(k2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JarFileUrlKey key(String spec) throws MalformedURLException {
|
||||||
|
return new JarFileUrlKey(new URL(spec));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue