Maintain classpath order in PropertiesLauncher

I think this is safe, judging by the integration tests, but I'm not
putting it in 1.2.x until we've had some feedback on it. The
integration tests actually had a bug that was masking this problem
because they were merging Properties from the whole classpath instead
of picking the first available resource (which is generally what
we do in Spring Boot applications for application.properties for
instance).

Fixes gh-3048
pull/3484/head
Dave Syer 9 years ago
parent a158baf50f
commit bfa816f2a3

@ -202,7 +202,7 @@ properties (System properties, environment variables, manifest entries or
|Key |Purpose |Key |Purpose
|`loader.path` |`loader.path`
|Comma-separated Classpath, e.g. `lib:${HOME}/app/lib`. |Comma-separated Classpath, e.g. `lib,${HOME}/app/lib`. Earlier entries take precedence, just like a regular `-classpath` on the `javac` command line.
|`loader.home` |`loader.home`
|Location of additional properties file, e.g. `file:///opt/app` |Location of additional properties file, e.g. `file:///opt/app`
@ -236,6 +236,7 @@ Environment variables can be capitalized with underscore separators instead of p
the default) as long as `loader.config.location` is not specified. the default) as long as `loader.config.location` is not specified.
* `loader.path` can contain directories (scanned recursively for jar and zip files), * `loader.path` can contain directories (scanned recursively for jar and zip files),
archive paths, or wildcard patterns (for the default JVM behavior). archive paths, or wildcard patterns (for the default JVM behavior).
* `loader.path` (if empty) defaults to `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.
* Placeholder replacement is done from System and environment variables plus the * Placeholder replacement is done from System and environment variables plus the
properties file itself on all values before use. properties file itself on all values before use.

@ -17,12 +17,13 @@
package org.springframework.boot.load.it.props; package org.springframework.boot.load.it.props;
import java.io.IOException; import java.io.IOException;
import java.util.Properties;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.core.io.ClassPathResource;
/** /**
* Spring configuration. * Spring configuration.
@ -37,7 +38,9 @@ public class SpringConfiguration {
@PostConstruct @PostConstruct
public void init() throws IOException { public void init() throws IOException {
String value = PropertiesLoaderUtils.loadAllProperties("application.properties").getProperty("message"); Properties props = new Properties();
props.load(new ClassPathResource("application.properties").getInputStream());
String value = props.getProperty("message");
if (value!=null) { if (value!=null) {
this.message = value; this.message = value;
} }

@ -26,7 +26,6 @@ assert out.contains('Hello Embedded Foo!'),
'With loader.path=.,lib should use the application.properties from the local filesystem\n' + out 'With loader.path=.,lib should use the application.properties from the local filesystem\n' + out
new File("${basedir}/target/application.properties").withWriter { it -> it << "message: Spam" } new File("${basedir}/target/application.properties").withWriter { it -> it << "message: Spam" }
out = exec("java -Dloader.path=target,.,lib -jar ${jarfile}") out = exec("java -Dloader.path=target,.,lib -jar ${jarfile}")
assert out.contains('Hello Embedded Spam!'), assert out.contains('Hello Embedded Spam!'),
'With loader.path=target,.,lib should use the application.properties from the target directory\n' + out 'With loader.path=target,.,lib should use the application.properties from the target directory\n' + out

@ -75,8 +75,6 @@ public abstract class Launcher {
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception { protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<URL>(archives.size()); List<URL> urls = new ArrayList<URL>(archives.size());
for (Archive archive : archives) { for (Archive archive : archives) {
// Add the current archive at end (it will be reversed and end up taking
// precedence)
urls.add(archive.getUrl()); urls.add(archive.getUrl());
} }
return createClassLoader(urls.toArray(new URL[urls.size()])); return createClassLoader(urls.toArray(new URL[urls.size()]));

@ -448,8 +448,6 @@ public class PropertiesLauncher extends Launcher {
} }
} }
addParentClassLoaderEntries(lib); addParentClassLoaderEntries(lib);
// Entries are reversed when added to the actual classpath
Collections.reverse(lib);
return lib; return lib;
} }

@ -134,6 +134,17 @@ public class PropertiesLauncherTests {
waitFor("Hello World"); waitFor("Hello World");
} }
@Test
public void testUserSpecifiedClassPathOrder() throws Exception {
System.setProperty("loader.path", "more-jars/app.jar,jars/app.jar");
System.setProperty("loader.classLoader", URLClassLoader.class.getName());
PropertiesLauncher launcher = new PropertiesLauncher();
assertEquals("[more-jars/app.jar, jars/app.jar]",
ReflectionTestUtils.getField(launcher, "paths").toString());
launcher.launch(new String[0]);
waitFor("Hello Other World");
}
@Test @Test
public void testCustomClassLoaderCreation() throws Exception { public void testCustomClassLoaderCreation() throws Exception {
System.setProperty("loader.classLoader", TestLoader.class.getName()); System.setProperty("loader.classLoader", TestLoader.class.getName());

Loading…
Cancel
Save