Extended PropertiesLauncher class location logic
Update `PropertiesLauncher` so that classes can be loaded outside of `BOOT-INF/classes`. You can use a subdirectory, or the root directory of an external jar (but not the parent archive to avoid issues with agents and awkward delegation models). Fixes gh-8480 Closes gh-8486
This commit is contained in:
@@ -735,7 +735,7 @@ public class WebMvcAutoConfiguration {
|
||||
static class DelegatingWebMvcValidator extends DelegatingValidator
|
||||
implements ApplicationContextAware, InitializingBean, DisposableBean {
|
||||
|
||||
public DelegatingWebMvcValidator(Validator targetValidator) {
|
||||
DelegatingWebMvcValidator(Validator targetValidator) {
|
||||
super(targetValidator);
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,6 @@ public class DelegatingValidatorTests {
|
||||
Object[] hints = { "foo", "bar" };
|
||||
this.delegating.validate(target, errors, hints);
|
||||
verify(this.delegate).validate(target, errors, hints);
|
||||
;
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -283,6 +283,8 @@ the `Main-Class` attribute and leave out `Start-Class`.
|
||||
* `loader.path` can contain directories (scanned recursively for jar and zip files),
|
||||
archive paths, a directory within an archive that is scanned for jar files (for
|
||||
example, `dependencies.jar!/lib`), or wildcard patterns (for the default JVM behavior).
|
||||
Archive paths can be relative to `loader.home`, or anywhere in the file system with a
|
||||
`jar:file:` prefix.
|
||||
* `loader.path` (if empty) defaults to `BOOT-INF/lib` (meaning a local directory or a
|
||||
nested one if running from an archive). Because of this `PropertiesLauncher` behaves the
|
||||
same as `JarLauncher` when no additional configuration is provided.
|
||||
|
||||
@@ -25,8 +25,10 @@ import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -299,11 +301,9 @@ public class PropertiesLauncher extends Launcher {
|
||||
List<String> paths = new ArrayList<String>();
|
||||
for (String path : commaSeparatedPaths.split(",")) {
|
||||
path = cleanupPath(path);
|
||||
// Empty path (i.e. the archive itself if running from a JAR) is always added
|
||||
// to the classpath so no need for it to be explicitly listed
|
||||
if (!path.equals("")) {
|
||||
paths.add(path);
|
||||
}
|
||||
// "" means the user wants root of archive but not current directory
|
||||
path = ("".equals(path) ? "/" : path);
|
||||
paths.add(path);
|
||||
}
|
||||
if (paths.isEmpty()) {
|
||||
paths.add("lib");
|
||||
@@ -336,7 +336,13 @@ public class PropertiesLauncher extends Launcher {
|
||||
|
||||
@Override
|
||||
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
|
||||
ClassLoader loader = super.createClassLoader(archives);
|
||||
Set<URL> urls = new LinkedHashSet<URL>(archives.size());
|
||||
for (Archive archive : archives) {
|
||||
urls.add(archive.getUrl());
|
||||
}
|
||||
ClassLoader loader = new LaunchedURLClassLoader(urls.toArray(new URL[0]),
|
||||
getClass().getClassLoader());
|
||||
debug("Classpath: " + urls);
|
||||
String customLoaderClassName = getProperty("loader.classLoader");
|
||||
if (customLoaderClassName != null) {
|
||||
loader = wrapWithCustomClassLoader(loader, customLoaderClassName);
|
||||
@@ -454,13 +460,15 @@ public class PropertiesLauncher extends Launcher {
|
||||
String root = cleanupPath(stripFileUrlPrefix(path));
|
||||
List<Archive> lib = new ArrayList<Archive>();
|
||||
File file = new File(root);
|
||||
if (!isAbsolutePath(root)) {
|
||||
file = new File(this.home, root);
|
||||
}
|
||||
if (file.isDirectory()) {
|
||||
debug("Adding classpath entries from " + file);
|
||||
Archive archive = new ExplodedArchive(file, false);
|
||||
lib.add(archive);
|
||||
if (!"/".equals(root)) {
|
||||
if (!isAbsolutePath(root)) {
|
||||
file = new File(this.home, root);
|
||||
}
|
||||
if (file.isDirectory()) {
|
||||
debug("Adding classpath entries from " + file);
|
||||
Archive archive = new ExplodedArchive(file, false);
|
||||
lib.add(archive);
|
||||
}
|
||||
}
|
||||
Archive archive = getArchive(file);
|
||||
if (archive != null) {
|
||||
@@ -488,24 +496,46 @@ public class PropertiesLauncher extends Launcher {
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Archive> getNestedArchives(String root) throws Exception {
|
||||
if (root.startsWith("/")
|
||||
|| this.parent.getUrl().equals(this.home.toURI().toURL())) {
|
||||
private List<Archive> getNestedArchives(String path) throws Exception {
|
||||
Archive parent = this.parent;
|
||||
String root = path;
|
||||
if (!root.equals("/") && root.startsWith("/")
|
||||
|| parent.getUrl().equals(this.home.toURI().toURL())) {
|
||||
// If home dir is same as parent archive, no need to add it twice.
|
||||
return null;
|
||||
}
|
||||
Archive parent = this.parent;
|
||||
if (root.startsWith("jar:file:") && root.contains("!")) {
|
||||
if (root.contains("!")) {
|
||||
int index = root.indexOf("!");
|
||||
String file = root.substring("jar:file:".length(), index);
|
||||
parent = new JarFileArchive(new File(file));
|
||||
File file = new File(this.home, root.substring(0, index));
|
||||
if (root.startsWith("jar:file:")) {
|
||||
file = new File(root.substring("jar:file:".length(), index));
|
||||
}
|
||||
parent = new JarFileArchive(file);
|
||||
root = root.substring(index + 1, root.length());
|
||||
while (root.startsWith("/")) {
|
||||
root = root.substring(1);
|
||||
}
|
||||
}
|
||||
if (root.endsWith(".jar")) {
|
||||
File file = new File(this.home, root);
|
||||
if (file.exists()) {
|
||||
parent = new JarFileArchive(file);
|
||||
root = "";
|
||||
}
|
||||
}
|
||||
if (root.equals("/") || root.equals("./") || root.equals(".")) {
|
||||
// The prefix for nested jars is actually empty if it's at the root
|
||||
root = "";
|
||||
}
|
||||
EntryFilter filter = new PrefixMatchingArchiveFilter(root);
|
||||
return parent.getNestedArchives(filter);
|
||||
List<Archive> archives = new ArrayList<Archive>(parent.getNestedArchives(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.
|
||||
archives.add(parent);
|
||||
}
|
||||
return archives;
|
||||
}
|
||||
|
||||
private void addNestedEntries(List<Archive> lib) {
|
||||
@@ -518,7 +548,7 @@ public class PropertiesLauncher extends Launcher {
|
||||
@Override
|
||||
public boolean matches(Entry entry) {
|
||||
if (entry.isDirectory()) {
|
||||
return entry.getName().startsWith(JarLauncher.BOOT_INF_CLASSES);
|
||||
return entry.getName().equals(JarLauncher.BOOT_INF_CLASSES);
|
||||
}
|
||||
return entry.getName().startsWith(JarLauncher.BOOT_INF_LIB);
|
||||
}
|
||||
@@ -607,6 +637,9 @@ public class PropertiesLauncher extends Launcher {
|
||||
|
||||
@Override
|
||||
public boolean matches(Entry entry) {
|
||||
if (entry.isDirectory()) {
|
||||
return entry.getName().equals(this.prefix);
|
||||
}
|
||||
return entry.getName().startsWith(this.prefix) && this.filter.matches(entry);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,12 +21,13 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -36,6 +37,9 @@ import org.junit.rules.TemporaryFolder;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.boot.loader.archive.ExplodedArchive;
|
||||
import org.springframework.boot.loader.archive.JarFileArchive;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -72,6 +76,7 @@ public class PropertiesLauncherTests {
|
||||
System.clearProperty("loader.config.name");
|
||||
System.clearProperty("loader.config.location");
|
||||
System.clearProperty("loader.system");
|
||||
System.clearProperty("loader.classLoader");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -131,6 +136,16 @@ public class PropertiesLauncherTests {
|
||||
.isEqualTo("[.]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserSpecifiedSlashPath() throws Exception {
|
||||
System.setProperty("loader.path", "jars/");
|
||||
PropertiesLauncher launcher = new PropertiesLauncher();
|
||||
assertThat(ReflectionTestUtils.getField(launcher, "paths").toString())
|
||||
.isEqualTo("[jars/]");
|
||||
List<Archive> archives = launcher.getClassPathArchives();
|
||||
assertThat(archives).areExactly(1, endingWith("app.jar!/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserSpecifiedWildcardPath() throws Exception {
|
||||
System.setProperty("loader.path", "jars/*");
|
||||
@@ -153,13 +168,44 @@ public class PropertiesLauncherTests {
|
||||
waitFor("Hello World");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserSpecifiedRootOfJarPath() throws Exception {
|
||||
System.setProperty("loader.path",
|
||||
"jar:file:./src/test/resources/nested-jars/app.jar!/");
|
||||
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();
|
||||
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
|
||||
assertThat(archives).areExactly(1, endingWith("app.jar!/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserSpecifiedRootOfJarPathWithDot() throws Exception {
|
||||
System.setProperty("loader.path", "nested-jars/app.jar!/./");
|
||||
PropertiesLauncher launcher = new PropertiesLauncher();
|
||||
List<Archive> archives = launcher.getClassPathArchives();
|
||||
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
|
||||
assertThat(archives).areExactly(1, endingWith("app.jar!/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public 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();
|
||||
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserSpecifiedJarFileWithNestedArchives() throws Exception {
|
||||
System.setProperty("loader.path", "nested-jars/app.jar");
|
||||
System.setProperty("loader.main", "demo.Application");
|
||||
PropertiesLauncher launcher = new PropertiesLauncher();
|
||||
launcher.launch(new String[0]);
|
||||
waitFor("Hello World");
|
||||
List<Archive> archives = launcher.getClassPathArchives();
|
||||
assertThat(archives).areExactly(1, endingWith("foo.jar!/"));
|
||||
assertThat(archives).areExactly(1, endingWith("app.jar!/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -209,11 +255,28 @@ public class PropertiesLauncherTests {
|
||||
public void testCustomClassLoaderCreation() throws Exception {
|
||||
System.setProperty("loader.classLoader", TestLoader.class.getName());
|
||||
PropertiesLauncher launcher = new PropertiesLauncher();
|
||||
ClassLoader loader = launcher.createClassLoader(Collections.<Archive>emptyList());
|
||||
ClassLoader loader = launcher.createClassLoader(archives());
|
||||
assertThat(loader).isNotNull();
|
||||
assertThat(loader.getClass().getName()).isEqualTo(TestLoader.class.getName());
|
||||
}
|
||||
|
||||
private List<Archive> archives() throws Exception {
|
||||
List<Archive> archives = new ArrayList<Archive>();
|
||||
String path = System.getProperty("java.class.path");
|
||||
for (String url : path.split(File.pathSeparator)) {
|
||||
archives.add(archive(url));
|
||||
}
|
||||
return archives;
|
||||
}
|
||||
|
||||
private Archive archive(String url) throws IOException {
|
||||
File file = new FileSystemResource(url).getFile();
|
||||
if (url.endsWith(".jar")) {
|
||||
return new JarFileArchive(file);
|
||||
}
|
||||
return new ExplodedArchive(file);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserSpecifiedConfigPathWins() throws Exception {
|
||||
|
||||
@@ -280,6 +343,17 @@ public class PropertiesLauncherTests {
|
||||
assertThat(timeout).as("Timed out waiting for (" + value + ")").isTrue();
|
||||
}
|
||||
|
||||
private Condition<Archive> endingWith(final String value) {
|
||||
return new Condition<Archive>() {
|
||||
|
||||
@Override
|
||||
public boolean matches(Archive archive) {
|
||||
return archive.toString().endsWith(value);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public static class TestLoader extends URLClassLoader {
|
||||
|
||||
public TestLoader(ClassLoader parent) {
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user