Allow user to specify classLoader as loader property
PropertiesLauncher now supports creating its own class loader from looader.classLoader property. It will succeed if the implementation specified has a default constructor or one that takes a parent class loader, or one that takes a URL[] and a parent class loader (like URLClassLoader).
This commit is contained in:
parent
f5f41fef5e
commit
033250195b
|
@ -233,11 +233,14 @@
|
|||
<archive>
|
||||
<manifest>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
<mainClass>org.springframework.boot.loader.JarLauncher</mainClass>
|
||||
<mainClass>org.springframework.boot.loader.PropertiesLauncher</mainClass>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<Start-Class>${start-class}</Start-Class>
|
||||
</manifestEntries>
|
||||
<manifestEntries>
|
||||
<Class-Loader>groovy.lang.GroovyClassLoader</Class-Loader>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
|
|
@ -33,7 +33,11 @@ import java.util.Arrays;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.boot.loader.archive.Archive.Entry;
|
||||
|
@ -116,6 +120,8 @@ public class PropertiesLauncher extends Launcher {
|
|||
|
||||
private static final List<String> DEFAULT_PATHS = Arrays.asList("lib/");
|
||||
|
||||
private static final Pattern WORD_SEPARATOR = Pattern.compile("\\W+");
|
||||
|
||||
private final File home;
|
||||
|
||||
private List<String> paths = new ArrayList<String>(DEFAULT_PATHS);
|
||||
|
@ -123,6 +129,9 @@ public class PropertiesLauncher extends Launcher {
|
|||
private Properties properties = new Properties();
|
||||
|
||||
public PropertiesLauncher() {
|
||||
if (!isDebug()) {
|
||||
this.logger.setLevel(Level.SEVERE);
|
||||
}
|
||||
try {
|
||||
this.home = getHomeDirectory();
|
||||
initializeProperties(this.home);
|
||||
|
@ -133,6 +142,22 @@ public class PropertiesLauncher extends Launcher {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isDebug() {
|
||||
String debug = System.getProperty("debug");
|
||||
if (debug != null && !"false".equals(debug)) {
|
||||
return true;
|
||||
}
|
||||
debug = System.getProperty("DEBUG");
|
||||
if (debug != null && !"false".equals(debug)) {
|
||||
return true;
|
||||
}
|
||||
debug = System.getenv("DEBUG");
|
||||
if (debug != null && !"false".equals(debug)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected File getHomeDirectory() {
|
||||
return new File(SystemPropertyUtils.resolvePlaceholders(System.getProperty(HOME,
|
||||
"${user.dir}")));
|
||||
|
@ -290,30 +315,82 @@ public class PropertiesLauncher extends Launcher {
|
|||
|
||||
@Override
|
||||
protected String getMainClass() throws Exception {
|
||||
String property = SystemPropertyUtils.getProperty(MAIN);
|
||||
if (property != null) {
|
||||
String mainClass = SystemPropertyUtils.resolvePlaceholders(property);
|
||||
this.logger.info("Main class from environment: " + mainClass);
|
||||
return mainClass;
|
||||
String mainClass = getProperty(MAIN, "Start-Class");
|
||||
if (mainClass == null) {
|
||||
throw new IllegalStateException("No '" + MAIN
|
||||
+ "' or 'Start-Class' specified");
|
||||
}
|
||||
if (this.properties.containsKey(MAIN)) {
|
||||
String mainClass = SystemPropertyUtils.resolvePlaceholders(this.properties
|
||||
.getProperty(MAIN));
|
||||
this.logger.info("Main class from properties: " + mainClass);
|
||||
return mainClass;
|
||||
return mainClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
|
||||
ClassLoader loader = super.createClassLoader(archives);
|
||||
String classLoaderType = getProperty("loader.classLoader");
|
||||
if (classLoaderType != null) {
|
||||
Class<?> type = Class.forName(classLoaderType, true, loader);
|
||||
try {
|
||||
loader = (ClassLoader) type.getConstructor(ClassLoader.class)
|
||||
.newInstance(loader);
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
try {
|
||||
loader = (ClassLoader) type.getConstructor(URL[].class,
|
||||
ClassLoader.class).newInstance(new URL[0], loader);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
loader = (ClassLoader) type.newInstance();
|
||||
}
|
||||
}
|
||||
this.logger.info("Using custom class loader: " + classLoaderType);
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
|
||||
private String getProperty(String propertyKey) throws Exception {
|
||||
return getProperty(propertyKey, null);
|
||||
}
|
||||
|
||||
private String getProperty(String propertyKey, String manifestKey) throws Exception {
|
||||
if (manifestKey == null) {
|
||||
manifestKey = propertyKey.replace(".", "-");
|
||||
manifestKey = toCamelCase(manifestKey);
|
||||
}
|
||||
String property = SystemPropertyUtils.getProperty(propertyKey);
|
||||
if (property != null) {
|
||||
String value = SystemPropertyUtils.resolvePlaceholders(property);
|
||||
this.logger.fine("Property '" + propertyKey + "' from environment: " + value);
|
||||
return value;
|
||||
}
|
||||
if (this.properties.containsKey(propertyKey)) {
|
||||
String value = SystemPropertyUtils.resolvePlaceholders(this.properties
|
||||
.getProperty(propertyKey));
|
||||
this.logger.fine("Property '" + propertyKey + "' from properties: " + value);
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
// Prefer home dir for MANIFEST if there is one
|
||||
String mainClass = new ExplodedArchive(this.home).getMainClass();
|
||||
this.logger.info("Main class from home directory manifest: " + mainClass);
|
||||
return mainClass;
|
||||
Manifest manifest = new ExplodedArchive(this.home).getManifest();
|
||||
if (manifest != null) {
|
||||
String value = manifest.getMainAttributes().getValue(manifestKey);
|
||||
this.logger.fine("Property '" + manifestKey
|
||||
+ "' from home directory manifest: " + value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
// Otherwise try the parent archive
|
||||
String mainClass = createArchive().getMainClass();
|
||||
this.logger.info("Main class from archive manifest: " + mainClass);
|
||||
return mainClass;
|
||||
}
|
||||
// Otherwise try the parent archive
|
||||
Manifest manifest = createArchive().getManifest();
|
||||
if (manifest != null) {
|
||||
String value = manifest.getMainAttributes().getValue(manifestKey);
|
||||
if (value != null) {
|
||||
this.logger.fine("Property '" + manifestKey + "' from archive manifest: "
|
||||
+ value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -444,6 +521,28 @@ public class PropertiesLauncher extends Launcher {
|
|||
new PropertiesLauncher().launch(args);
|
||||
}
|
||||
|
||||
public static String toCamelCase(CharSequence string) {
|
||||
if (string == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Matcher matcher = WORD_SEPARATOR.matcher(string);
|
||||
int pos = 0;
|
||||
while (matcher.find()) {
|
||||
builder.append(capitalize(string.subSequence(pos, matcher.end()).toString()));
|
||||
pos = matcher.end();
|
||||
}
|
||||
builder.append(capitalize(string.subSequence(pos, string.length()).toString()));
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static Object capitalize(String str) {
|
||||
StringBuilder sb = new StringBuilder(str.length());
|
||||
sb.append(Character.toUpperCase(str.charAt(0)));
|
||||
sb.append(str.substring(1));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience class for finding nested archives (archive entries that can be
|
||||
* classpath entries).
|
||||
|
|
|
@ -18,14 +18,19 @@ package org.springframework.boot.loader;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
|
@ -98,6 +103,27 @@ public class PropertiesLauncherTests {
|
|||
waitFor("Hello World");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserSpecifiedClassLoader() throws Exception {
|
||||
System.setProperty("loader.path", "jars/app.jar");
|
||||
System.setProperty("loader.classLoader", URLClassLoader.class.getName());
|
||||
PropertiesLauncher launcher = new PropertiesLauncher();
|
||||
assertEquals("[jars/app.jar]", ReflectionTestUtils.getField(launcher, "paths")
|
||||
.toString());
|
||||
launcher.launch(new String[0]);
|
||||
waitFor("Hello World");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomClassLoaderCreation() throws Exception {
|
||||
System.setProperty("loader.classLoader", TestLoader.class.getName());
|
||||
PropertiesLauncher launcher = new PropertiesLauncher();
|
||||
ClassLoader loader = launcher
|
||||
.createClassLoader(Collections.<Archive> emptyList());
|
||||
assertNotNull(loader);
|
||||
assertEquals(TestLoader.class.getName(), loader.getClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserSpecifiedConfigPathWins() throws Exception {
|
||||
|
||||
|
@ -132,4 +158,16 @@ public class PropertiesLauncherTests {
|
|||
assertTrue("Timed out waiting for (" + value + ")", timeout);
|
||||
}
|
||||
|
||||
public static class TestLoader extends URLClassLoader {
|
||||
|
||||
public TestLoader(ClassLoader parent) {
|
||||
super(new URL[0], parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
return super.findClass(name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue