Skip to content

Commit ce77f48

Browse files
committed
Fall back to JVM's class path when TCCL is not a URLClassLoader
Previously, DevTools assumed that the TCCL was a URLClassLoader when trying to determine the URLs that it should examine to determine the locations that should be watched for triggering a restart. This fails on Java 9 as the TCCL is not a URLClassLoader. This commit updates the logic that determines the changeable URLs to fall back to examining the JVM's class path when the TCCL is not a URLClassLoader, typically because the application is running on Java 9. This fall back isn't a direct equivalent of the behaviour on Java 8 as the class path of the TCCL and the class path with which the JVM was launched may not be the same. However, I consider the fix to be reasonable for two reasons: 1. In reality, the class path of the TCCL and the class path with which the JVM was launched are the same. 2. There appears to be now to get the URLs on the class path of the TCCL on Java 9. There is a URLClassPath field, however Java 9's access restrictions prevent us from using it even if we resort to reflection. Closes gh-10454
1 parent b152b98 commit ce77f48

File tree

5 files changed

+37
-28
lines changed

5 files changed

+37
-28
lines changed

spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/ChangeableUrls.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.lang.management.ManagementFactory;
2122
import java.net.MalformedURLException;
2223
import java.net.URL;
2324
import java.net.URLClassLoader;
@@ -29,6 +30,7 @@
2930
import java.util.jar.Attributes;
3031
import java.util.jar.JarFile;
3132
import java.util.jar.Manifest;
33+
import java.util.stream.Stream;
3234

3335
import org.apache.commons.logging.Log;
3436
import org.apache.commons.logging.LogFactory;
@@ -89,15 +91,35 @@ public String toString() {
8991
return this.urls.toString();
9092
}
9193

92-
public static ChangeableUrls fromUrlClassLoader(URLClassLoader classLoader) {
94+
public static ChangeableUrls fromClassLoader(ClassLoader classLoader) {
9395
List<URL> urls = new ArrayList<>();
94-
for (URL url : classLoader.getURLs()) {
96+
for (URL url : urlsFromClassLoader(classLoader)) {
9597
urls.add(url);
9698
urls.addAll(getUrlsFromClassPathOfJarManifestIfPossible(url));
9799
}
98100
return fromUrls(urls);
99101
}
100102

103+
private static URL[] urlsFromClassLoader(ClassLoader classLoader) {
104+
if (classLoader instanceof URLClassLoader) {
105+
return ((URLClassLoader) classLoader).getURLs();
106+
}
107+
return Stream
108+
.of(ManagementFactory.getRuntimeMXBean().getClassPath()
109+
.split(File.pathSeparator))
110+
.map(ChangeableUrls::toURL).toArray(URL[]::new);
111+
}
112+
113+
private static URL toURL(String classPathEntry) {
114+
try {
115+
return new File(classPathEntry).toURI().toURL();
116+
}
117+
catch (MalformedURLException ex) {
118+
throw new IllegalArgumentException(
119+
"URL could not be created from '" + classPathEntry + "'", ex);
120+
}
121+
}
122+
101123
private static List<URL> getUrlsFromClassPathOfJarManifestIfPossible(URL url) {
102124
JarFile jarFile = getJarFileIfPossible(url);
103125
if (jarFile == null) {

spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/DefaultRestartInitializer.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
package org.springframework.boot.devtools.restart;
1818

1919
import java.net.URL;
20-
import java.net.URLClassLoader;
2120
import java.util.Collections;
2221
import java.util.LinkedHashSet;
2322
import java.util.Set;
23+
import java.util.stream.Stream;
2424

2525
/**
2626
* Default {@link RestartInitializer} that only enable initial restart when running a
@@ -53,7 +53,9 @@ public URL[] getInitialUrls(Thread thread) {
5353
return null;
5454
}
5555
}
56-
return getUrls(thread);
56+
URL[] urls = getUrls(thread);
57+
Stream.of(urls).forEach(System.out::println);
58+
return urls;
5759
}
5860

5961
/**
@@ -89,9 +91,7 @@ protected boolean isSkippedStackElement(StackTraceElement element) {
8991
* @return the URLs
9092
*/
9193
protected URL[] getUrls(Thread thread) {
92-
return ChangeableUrls
93-
.fromUrlClassLoader((URLClassLoader) thread.getContextClassLoader())
94-
.toArray();
94+
return ChangeableUrls.fromClassLoader(thread.getContextClassLoader()).toArray();
9595
}
9696

9797
}

spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/ChangeableUrlsTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public void urlsFromJarClassPathAreConsidered() throws Exception {
8585
.mkdirs();
8686
new File(jarWithClassPath.getParentFile(), "project-web/target/classes").mkdirs();
8787
ChangeableUrls urls = ChangeableUrls
88-
.fromUrlClassLoader(new URLClassLoader(new URL[] {
88+
.fromClassLoader(new URLClassLoader(new URL[] {
8989
jarWithClassPath.toURI().toURL(), makeJarFileWithNoManifest() }));
9090
assertThat(urls.toList()).containsExactly(
9191
new URL(jarWithClassPath.toURI().toURL(), "project-core/target/classes/"),

spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/DefaultRestartInitializerTests.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2016 the original author or authors.
2+
* Copyright 2012-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -86,6 +86,12 @@ public void skipsDueToCucumber() throws Exception {
8686
testSkipStack("cucumber.runtime.Runtime.run", true);
8787
}
8888

89+
@Test
90+
public void urlsCanBeRetrieved() {
91+
assertThat(new DefaultRestartInitializer().getUrls(Thread.currentThread()))
92+
.isNotEmpty();
93+
}
94+
8995
private void testSkipStack(String className, boolean expected) {
9096
MockRestartInitializer initializer = new MockRestartInitializer(true);
9197
StackTraceElement element = new StackTraceElement(className, "someMethod",

spring-boot-integration-tests/spring-boot-devtools-tests/pom.xml

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -87,23 +87,4 @@
8787
</plugins>
8888
</pluginManagement>
8989
</build>
90-
<profiles>
91-
<profile>
92-
<id>java9</id>
93-
<activation>
94-
<jdk>9</jdk>
95-
</activation>
96-
<build>
97-
<plugins>
98-
<plugin>
99-
<groupId>org.apache.maven.plugins</groupId>
100-
<artifactId>maven-surefire-plugin</artifactId>
101-
<configuration>
102-
<skipTests>true</skipTests>
103-
</configuration>
104-
</plugin>
105-
</plugins>
106-
</build>
107-
</profile>
108-
</profiles>
10990
</project>

0 commit comments

Comments
 (0)