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