From 3701cce88fe39f4c85cf514db9f9a7c3d6ce56fa Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 2 Mar 2017 08:50:07 +0000 Subject: [PATCH 1/2] Allow loader.path to refer to nested jars Previously, each entry in loader.path could only refer to a standard jar file. Refering to such a jar would add all of the classes in the root of the jar to the class path. This commit adds support for referencing a directory within a jar file that contains one or more nested jars. For example: $ java -jar -Dloader.path='jar:file:./lib.jar/!BOOT-INF/lib' my.jar This will add all of the classes in all of that jars in the BOOT-INF/lib directory of lib.jar to the class path. See gh-8334 --- .../it/executable-props-lib/loader.properties | 1 + .../src/it/executable-props-lib/pom.xml | 82 +++++++++++++++++++ .../main/assembly/jar-with-dependencies.xml | 27 ++++++ .../src/main/resources/application.properties | 1 + .../src/it/executable-props-lib/verify.groovy | 17 ++++ .../boot/loader/PropertiesLauncher.java | 70 ++++------------ 6 files changed, 143 insertions(+), 55 deletions(-) create mode 100644 spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/loader.properties create mode 100644 spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/pom.xml create mode 100644 spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/jar-with-dependencies.xml create mode 100644 spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/resources/application.properties create mode 100644 spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/verify.groovy 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..1099d674566 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/loader.properties @@ -0,0 +1 @@ +loader.path=jar:file:../executable-props/target/executable-props-0.0.1.BUILD-SNAPSHOT-full.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..972dfb4649b --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/pom.xml @@ -0,0 +1,82 @@ + + + 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}/assembly + + + + + + maven-assembly-plugin + 2.4 + + + src/main/assembly/jar-with-dependencies.xml + + + + org.springframework.boot.loader.PropertiesLauncher + + + org.springframework.boot.load.it.props.EmbeddedJarStarter + + + + + + jar-with-dependencies + package + + single + + + + + + + + + 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/jar-with-dependencies.xml b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/jar-with-dependencies.xml new file mode 100644 index 00000000000..1120acb7e2f --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/jar-with-dependencies.xml @@ -0,0 +1,27 @@ + + + full + + jar + + false + + + + + ${project.groupId}:${project.artifactId} + + BOOT-INF/classes + true + + + + + ${project.build.directory}/assembly + / + + + 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..ccb2e78f687 --- /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-full.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 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 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); - } - }); - } - - } - } From 6673d8eebc8b618e294dd99e05a76457a9842376 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 2 Mar 2017 11:17:24 +0000 Subject: [PATCH 2/2] Polish "Allow loader.path to refer to nested jars" Closes gh-8334 Closes gh-8465 --- .../appendix-executable-jar-format.adoc | 6 ++- .../it/executable-props-lib/loader.properties | 2 +- .../src/it/executable-props-lib/pom.xml | 52 ++++++++++++------ .../{jar-with-dependencies.xml => app.xml} | 4 +- .../src/main/assembly/dependencies.xml | 17 ++++++ .../launcher/it/props/EmbeddedJarStarter.java | 34 ++++++++++++ .../it/props/SpringConfiguration.java | 54 +++++++++++++++++++ .../src/it/executable-props-lib/verify.groovy | 6 +-- 8 files changed, 152 insertions(+), 23 deletions(-) rename spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/{jar-with-dependencies.xml => app.xml} (90%) create mode 100644 spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/dependencies.xml create mode 100644 spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/EmbeddedJarStarter.java create mode 100644 spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/SpringConfiguration.java 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 index 1099d674566..48a576ba043 100644 --- 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 @@ -1 +1 @@ -loader.path=jar:file:../executable-props/target/executable-props-0.0.1.BUILD-SNAPSHOT-full.jar/!BOOT-INF/lib \ No newline at end of file +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 index 972dfb4649b..50b0f033e9b 100644 --- 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 @@ -39,7 +39,17 @@ jar - ${project.build.directory}/assembly + ${project.build.directory}/app-assembly + + + + copy + prepare-package + + copy-dependencies + + + ${project.build.directory}/dependencies-assembly/BOOT-INF/lib @@ -47,26 +57,38 @@ maven-assembly-plugin 2.4 - - - src/main/assembly/jar-with-dependencies.xml - - - - org.springframework.boot.loader.PropertiesLauncher - - - org.springframework.boot.load.it.props.EmbeddedJarStarter - - - - jar-with-dependencies + 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 + + diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/jar-with-dependencies.xml b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/app.xml similarity index 90% rename from spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/jar-with-dependencies.xml rename to spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/app.xml index 1120acb7e2f..d332aebc34d 100644 --- a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/jar-with-dependencies.xml +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/app.xml @@ -3,7 +3,7 @@ xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd"> - full + app jar @@ -20,7 +20,7 @@ - ${project.build.directory}/assembly + ${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/verify.groovy b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/verify.groovy index ccb2e78f687..b855fbe7c7a 100644 --- 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 @@ -1,4 +1,4 @@ -def jarfile = './target/executable-props-lib-0.0.1.BUILD-SNAPSHOT-full.jar' +def jarfile = './target/executable-props-lib-0.0.1.BUILD-SNAPSHOT-app.jar' new File("${basedir}/application.properties").delete() @@ -10,8 +10,8 @@ String exec(String command) { String out = exec("java -jar ${jarfile}") assert out.contains('Hello Embedded World!'), - 'Using -jar my.jar should use the application.properties from the jar\n' + out + '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 use the application.properties from the jar\n' + out + 'Using -cp my.jar with PropertiesLauncher should load dependencies from separate jar and use the application.properties from the jar\n' + out