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:
Dave Syer 2013-12-31 14:35:51 +00:00
parent f5f41fef5e
commit 033250195b
3 changed files with 158 additions and 18 deletions

View File

@ -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>

View File

@ -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).

View File

@ -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);
}
}
}