Fix class loading problems when CLI extensions are installed
Previously, CLI extensions where installed into the CLI's lib directory which meant that they were on the class path of the app class loader. Following the change to an executable jar's packaging, this meant that they could not see classes in the CLI and a ClassNotFoundException would result. This commit updates the CLI to install extensions into lib/ext and load commands using a new ClassLoader that has all of the jars in lib/ext on its class path and that uses the launch class loader as its parent. Closes gh-6615
This commit is contained in:
parent
b2420be886
commit
270530c4fd
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,6 +16,12 @@
|
|||
|
||||
package org.springframework.boot.cli;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import org.springframework.boot.cli.command.CommandFactory;
|
||||
|
@ -25,6 +31,7 @@ import org.springframework.boot.cli.command.core.HintCommand;
|
|||
import org.springframework.boot.cli.command.core.VersionCommand;
|
||||
import org.springframework.boot.cli.command.shell.ShellCommand;
|
||||
import org.springframework.boot.loader.tools.LogbackInitializer;
|
||||
import org.springframework.util.SystemPropertyUtils;
|
||||
|
||||
/**
|
||||
* Spring Command Line Interface. This is the main entry-point for the Spring command line
|
||||
|
@ -60,10 +67,34 @@ public final class SpringCli {
|
|||
|
||||
private static void addServiceLoaderCommands(CommandRunner runner) {
|
||||
ServiceLoader<CommandFactory> factories = ServiceLoader.load(CommandFactory.class,
|
||||
runner.getClass().getClassLoader());
|
||||
createCommandClassLoader(runner));
|
||||
for (CommandFactory factory : factories) {
|
||||
runner.addCommands(factory.getCommands());
|
||||
}
|
||||
}
|
||||
|
||||
private static URLClassLoader createCommandClassLoader(CommandRunner runner) {
|
||||
return new URLClassLoader(getExtensionURLs(), runner.getClass().getClassLoader());
|
||||
}
|
||||
|
||||
private static URL[] getExtensionURLs() {
|
||||
List<URL> urls = new ArrayList<URL>();
|
||||
String home = SystemPropertyUtils
|
||||
.resolvePlaceholders("${spring.home:${SPRING_HOME:.}}");
|
||||
File extDirectory = new File(new File(home, "lib"), "ext");
|
||||
if (extDirectory.isDirectory()) {
|
||||
for (File file : extDirectory.listFiles()) {
|
||||
if (file.getName().endsWith(".jar")) {
|
||||
try {
|
||||
urls.add(file.toURI().toURL());
|
||||
}
|
||||
catch (MalformedURLException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return urls.toArray(new URL[urls.size()]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* Copyright 2013-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -37,7 +37,7 @@ import org.springframework.util.Assert;
|
|||
public class InstallCommand extends OptionParsingCommand {
|
||||
|
||||
public InstallCommand() {
|
||||
super("install", "Install dependencies to the lib directory",
|
||||
super("install", "Install dependencies to the lib/ext directory",
|
||||
new InstallOptionHandler());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -92,7 +92,7 @@ class Installer {
|
|||
}
|
||||
|
||||
public void install(List<String> artifactIdentifiers) throws Exception {
|
||||
File libDirectory = getDefaultLibDirectory();
|
||||
File libDirectory = getDefaultExtDirectory();
|
||||
libDirectory.mkdirs();
|
||||
Log.info("Installing into: " + libDirectory);
|
||||
List<File> artifactFiles = this.dependencyResolver.resolve(artifactIdentifiers);
|
||||
|
@ -125,13 +125,13 @@ class Installer {
|
|||
}
|
||||
|
||||
public void uninstall(List<String> artifactIdentifiers) throws Exception {
|
||||
File libDirectory = getDefaultLibDirectory();
|
||||
Log.info("Uninstalling from: " + libDirectory);
|
||||
File extDirectory = getDefaultExtDirectory();
|
||||
Log.info("Uninstalling from: " + extDirectory);
|
||||
List<File> artifactFiles = this.dependencyResolver.resolve(artifactIdentifiers);
|
||||
for (File artifactFile : artifactFiles) {
|
||||
int installCount = getInstallCount(artifactFile);
|
||||
if (installCount <= 1) {
|
||||
new File(libDirectory, artifactFile.getName()).delete();
|
||||
new File(extDirectory, artifactFile.getName()).delete();
|
||||
}
|
||||
setInstallCount(artifactFile, installCount - 1);
|
||||
}
|
||||
|
@ -139,23 +139,30 @@ class Installer {
|
|||
}
|
||||
|
||||
public void uninstallAll() throws Exception {
|
||||
File libDirectory = getDefaultLibDirectory();
|
||||
Log.info("Uninstalling from: " + libDirectory);
|
||||
File extDirectory = getDefaultExtDirectory();
|
||||
Log.info("Uninstalling from: " + extDirectory);
|
||||
for (String name : this.installCounts.stringPropertyNames()) {
|
||||
new File(libDirectory, name).delete();
|
||||
new File(extDirectory, name).delete();
|
||||
}
|
||||
this.installCounts.clear();
|
||||
saveInstallCounts();
|
||||
}
|
||||
|
||||
private File getDefaultLibDirectory() {
|
||||
private File getDefaultExtDirectory() {
|
||||
String home = SystemPropertyUtils
|
||||
.resolvePlaceholders("${spring.home:${SPRING_HOME:.}}");
|
||||
return new File(home, "lib");
|
||||
File extDirectory = new File(new File(home, "lib"), "ext");
|
||||
if (!extDirectory.isDirectory()) {
|
||||
if (!extDirectory.mkdirs()) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to create ext directory " + extDirectory);
|
||||
}
|
||||
}
|
||||
return extDirectory;
|
||||
}
|
||||
|
||||
private File getInstalled() {
|
||||
return new File(getDefaultLibDirectory(), ".installed");
|
||||
return new File(getDefaultExtDirectory(), ".installed");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -28,7 +28,7 @@ import org.springframework.boot.cli.command.status.ExitStatus;
|
|||
import org.springframework.boot.cli.util.Log;
|
||||
|
||||
/**
|
||||
* {@link Command} to uninstall dependencies from the CLI's lib directory.
|
||||
* {@link Command} to uninstall dependencies from the CLI's lib/ext directory.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
|
@ -37,7 +37,7 @@ import org.springframework.boot.cli.util.Log;
|
|||
public class UninstallCommand extends OptionParsingCommand {
|
||||
|
||||
public UninstallCommand() {
|
||||
super("uninstall", "Uninstall dependencies from the lib directory",
|
||||
super("uninstall", "Uninstall dependencies from the lib/ext directory",
|
||||
new UninstallOptionHandler());
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ public class InstallerTests {
|
|||
File foo = createTemporaryFile("foo.jar");
|
||||
given(this.resolver.resolve(Arrays.asList("foo"))).willReturn(Arrays.asList(foo));
|
||||
this.installer.install(Arrays.asList("foo"));
|
||||
assertThat(getNamesOfFilesInLib()).containsOnly("foo.jar", ".installed");
|
||||
assertThat(getNamesOfFilesInLibExt()).containsOnly("foo.jar", ".installed");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -75,7 +75,7 @@ public class InstallerTests {
|
|||
given(this.resolver.resolve(Arrays.asList("foo"))).willReturn(Arrays.asList(foo));
|
||||
this.installer.install(Arrays.asList("foo"));
|
||||
this.installer.uninstall(Arrays.asList("foo"));
|
||||
assertThat(getNamesOfFilesInLib()).contains(".installed");
|
||||
assertThat(getNamesOfFilesInLibExt()).contains(".installed");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -88,16 +88,16 @@ public class InstallerTests {
|
|||
given(this.resolver.resolve(Arrays.asList("charlie")))
|
||||
.willReturn(Arrays.asList(charlie, alpha));
|
||||
this.installer.install(Arrays.asList("bravo"));
|
||||
assertThat(getNamesOfFilesInLib()).containsOnly("alpha.jar", "bravo.jar",
|
||||
assertThat(getNamesOfFilesInLibExt()).containsOnly("alpha.jar", "bravo.jar",
|
||||
".installed");
|
||||
this.installer.install(Arrays.asList("charlie"));
|
||||
assertThat(getNamesOfFilesInLib()).containsOnly("alpha.jar", "bravo.jar",
|
||||
assertThat(getNamesOfFilesInLibExt()).containsOnly("alpha.jar", "bravo.jar",
|
||||
"charlie.jar", ".installed");
|
||||
this.installer.uninstall(Arrays.asList("bravo"));
|
||||
assertThat(getNamesOfFilesInLib()).containsOnly("alpha.jar", "charlie.jar",
|
||||
assertThat(getNamesOfFilesInLibExt()).containsOnly("alpha.jar", "charlie.jar",
|
||||
".installed");
|
||||
this.installer.uninstall(Arrays.asList("charlie"));
|
||||
assertThat(getNamesOfFilesInLib()).containsOnly(".installed");
|
||||
assertThat(getNamesOfFilesInLibExt()).containsOnly(".installed");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -111,15 +111,15 @@ public class InstallerTests {
|
|||
.willReturn(Arrays.asList(charlie, alpha));
|
||||
this.installer.install(Arrays.asList("bravo"));
|
||||
this.installer.install(Arrays.asList("charlie"));
|
||||
assertThat(getNamesOfFilesInLib()).containsOnly("alpha.jar", "bravo.jar",
|
||||
assertThat(getNamesOfFilesInLibExt()).containsOnly("alpha.jar", "bravo.jar",
|
||||
"charlie.jar", ".installed");
|
||||
this.installer.uninstallAll();
|
||||
assertThat(getNamesOfFilesInLib()).containsOnly(".installed");
|
||||
assertThat(getNamesOfFilesInLibExt()).containsOnly(".installed");
|
||||
}
|
||||
|
||||
private Set<String> getNamesOfFilesInLib() {
|
||||
private Set<String> getNamesOfFilesInLibExt() {
|
||||
Set<String> names = new HashSet<String>();
|
||||
for (File file : new File("target/lib").listFiles()) {
|
||||
for (File file : new File("target/lib/ext").listFiles()) {
|
||||
names.add(file.getName());
|
||||
}
|
||||
return names;
|
||||
|
|
Loading…
Reference in New Issue