diff --git a/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc b/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc
index 7a57cd6471d..1a0732358d7 100644
--- a/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc
+++ b/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc
@@ -147,7 +147,8 @@ files in directories (as opposed to explicitly on the classpath). In the case of
you just add extra jars in those locations if you want more. The `PropertiesLauncher`
looks in `BOOT-INF/lib/` in your application archive by default, but you can add
additional locations by setting an environment variable `LOADER_PATH` or `loader.path`
-in `loader.properties` (comma-separated list of directories or archives).
+in `loader.properties` (comma-separated list of directories, archives, or directories
+within archives).
@@ -280,7 +281,8 @@ the `Main-Class` attribute and leave out `Start-Class`.
* `loader.home` is only the directory location of an additional properties file
(overriding the default) as long as `loader.config.location` is not specified.
* `loader.path` can contain directories (scanned recursively for jar and zip files),
- archive paths, or wildcard patterns (for the default JVM behavior).
+ 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).
* `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.
diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/loader.properties b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/loader.properties
new file mode 100644
index 00000000000..48a576ba043
--- /dev/null
+++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/loader.properties
@@ -0,0 +1 @@
+loader.path=jar:file:target/executable-props-lib-0.0.1.BUILD-SNAPSHOT-dependencies.jar/!BOOT-INF/lib
\ No newline at end of file
diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/pom.xml b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/pom.xml
new file mode 100644
index 00000000000..50b0f033e9b
--- /dev/null
+++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/pom.xml
@@ -0,0 +1,104 @@
+
+
+ 4.0.0
+ org.springframework.boot.launcher.it
+ executable-props-lib
+ 0.0.1.BUILD-SNAPSHOT
+
+ UTF-8
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.1
+
+ 1.6
+ 1.6
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 2.10
+
+
+ unpack
+ prepare-package
+
+ unpack
+
+
+
+
+ @project.groupId@
+ @project.artifactId@
+ @project.version@
+ jar
+
+
+ ${project.build.directory}/app-assembly
+
+
+
+ copy
+ prepare-package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/dependencies-assembly/BOOT-INF/lib
+
+
+
+
+
+ maven-assembly-plugin
+ 2.4
+
+
+ app
+ package
+
+ single
+
+
+
+ src/main/assembly/app.xml
+
+
+
+ org.springframework.boot.loader.PropertiesLauncher
+
+
+ org.springframework.boot.launcher.it.props.EmbeddedJarStarter
+
+
+
+
+
+ depedendencies
+ package
+
+ single
+
+
+
+ src/main/assembly/dependencies.xml
+
+
+
+
+
+
+
+
+
+ org.springframework
+ spring-context
+ 4.1.4.RELEASE
+
+
+
diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/app.xml b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/app.xml
new file mode 100644
index 00000000000..d332aebc34d
--- /dev/null
+++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/app.xml
@@ -0,0 +1,27 @@
+
+
+ app
+
+ jar
+
+ false
+
+
+
+
+ ${project.groupId}:${project.artifactId}
+
+ BOOT-INF/classes
+ true
+
+
+
+
+ ${project.build.directory}/app-assembly
+ /
+
+
+
diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/dependencies.xml b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/dependencies.xml
new file mode 100644
index 00000000000..5044392b3e9
--- /dev/null
+++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/dependencies.xml
@@ -0,0 +1,17 @@
+
+
+ dependencies
+
+ jar
+
+ false
+
+
+ ${project.build.directory}/dependencies-assembly
+ /
+
+
+
diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/EmbeddedJarStarter.java b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/EmbeddedJarStarter.java
new file mode 100644
index 00000000000..da2c1bf2a90
--- /dev/null
+++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/EmbeddedJarStarter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012-2017 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.launcher.it.props;
+
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+/**
+ * Main class to start the embedded server.
+ *
+ * @author Dave Syer
+ */
+public final class EmbeddedJarStarter {
+
+ public static void main(String[] args) throws Exception {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
+ context.getBean(SpringConfiguration.class).run(args);
+ context.close();
+ }
+
+}
diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/SpringConfiguration.java b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/SpringConfiguration.java
new file mode 100644
index 00000000000..f05bae73fdc
--- /dev/null
+++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/SpringConfiguration.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012-2017 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.launcher.it.props;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.annotation.PostConstruct;
+
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+
+/**
+ * Spring configuration.
+ *
+ * @author Dave Syer
+ */
+@Configuration
+@ComponentScan
+public class SpringConfiguration {
+
+ private String message = "Jar";
+
+ @PostConstruct
+ public void init() throws IOException {
+ Properties props = new Properties();
+ props.load(new ClassPathResource("application.properties").getInputStream());
+ String value = props.getProperty("message");
+ if (value!=null) {
+ this.message = value;
+ }
+
+ }
+
+ public void run(String... args) {
+ System.err.println("Hello Embedded " + this.message + "!");
+ }
+
+}
diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/resources/application.properties b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/resources/application.properties
new file mode 100644
index 00000000000..c11051e3477
--- /dev/null
+++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/resources/application.properties
@@ -0,0 +1 @@
+message: World
\ No newline at end of file
diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/verify.groovy b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/verify.groovy
new file mode 100644
index 00000000000..b855fbe7c7a
--- /dev/null
+++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/verify.groovy
@@ -0,0 +1,17 @@
+def jarfile = './target/executable-props-lib-0.0.1.BUILD-SNAPSHOT-app.jar'
+
+new File("${basedir}/application.properties").delete()
+
+String exec(String command) {
+ def proc = command.execute([], basedir)
+ proc.waitFor()
+ proc.err.text
+}
+
+String out = exec("java -jar ${jarfile}")
+assert out.contains('Hello Embedded World!'),
+ 'Using -jar my.jar should load dependencies from separate jar and use the application.properties from the jar\n' + out
+
+out = exec("java -cp ${jarfile} org.springframework.boot.loader.PropertiesLauncher")
+assert out.contains('Hello Embedded World!'),
+ 'Using -cp my.jar with PropertiesLauncher should load dependencies from separate jar and use the application.properties from the jar\n' + out
diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java
index 083f639214f..01253279ac3 100755
--- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java
+++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java
@@ -21,12 +21,10 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.jar.Manifest;
@@ -469,10 +467,10 @@ public class PropertiesLauncher extends Launcher {
debug("Adding classpath entries from archive " + archive.getUrl() + root);
lib.add(archive);
}
- Archive nested = getNestedArchive(root);
- if (nested != null) {
+ List nestedArchives = getNestedArchives(root);
+ if (nestedArchives != null) {
debug("Adding classpath entries from nested " + root);
- lib.add(nested);
+ lib.addAll(nestedArchives);
}
return lib;
}
@@ -490,19 +488,24 @@ public class PropertiesLauncher extends Launcher {
return null;
}
- private Archive getNestedArchive(String root) throws Exception {
+ private List getNestedArchives(String root) throws Exception {
if (root.startsWith("/")
|| this.parent.getUrl().equals(this.home.toURI().toURL())) {
// If home dir is same as parent archive, no need to add it twice.
return null;
}
- EntryFilter filter = new PrefixMatchingArchiveFilter(root);
- if (this.parent.getNestedArchives(filter).isEmpty()) {
- return null;
+ Archive parent = this.parent;
+ if (root.startsWith("jar:file:") && root.contains("!")) {
+ int index = root.indexOf("!");
+ String file = root.substring("jar:file:".length(), index);
+ parent = new JarFileArchive(new File(file));
+ root = root.substring(index + 1, root.length());
+ while (root.startsWith("/")) {
+ root = root.substring(1);
+ }
}
- // If there are more archives nested in this subdirectory (root) then create a new
- // virtual archive for them, and have it added to the classpath
- return new FilteredArchive(this.parent, filter);
+ EntryFilter filter = new PrefixMatchingArchiveFilter(root);
+ return parent.getNestedArchives(filter);
}
private void addNestedEntries(List lib) {
@@ -626,47 +629,4 @@ public class PropertiesLauncher extends Launcher {
}
- /**
- * Decorator to apply an {@link Archive.EntryFilter} to an existing {@link Archive}.
- */
- private static class FilteredArchive implements Archive {
-
- private final Archive parent;
-
- private final EntryFilter filter;
-
- FilteredArchive(Archive parent, EntryFilter filter) {
- this.parent = parent;
- this.filter = filter;
- }
-
- @Override
- public URL getUrl() throws MalformedURLException {
- return this.parent.getUrl();
- }
-
- @Override
- public Manifest getManifest() throws IOException {
- return this.parent.getManifest();
- }
-
- @Override
- public Iterator iterator() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public List getNestedArchives(final EntryFilter filter)
- throws IOException {
- return this.parent.getNestedArchives(new EntryFilter() {
- @Override
- public boolean matches(Entry entry) {
- return FilteredArchive.this.filter.matches(entry)
- && filter.matches(entry);
- }
- });
- }
-
- }
-
}