Skip to content

Commit caefb23

Browse files
committed
Merge pull request #8465 from Dave Syer
* gh-8334: Polish "Allow loader.path to refer to nested jars" Allow loader.path to refer to nested jars
2 parents 605b0ae + 6673d8e commit caefb23

File tree

10 files changed

+274
-57
lines changed

10 files changed

+274
-57
lines changed

spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ files in directories (as opposed to explicitly on the classpath). In the case of
147147
you just add extra jars in those locations if you want more. The `PropertiesLauncher`
148148
looks in `BOOT-INF/lib/` in your application archive by default, but you can add
149149
additional locations by setting an environment variable `LOADER_PATH` or `loader.path`
150-
in `loader.properties` (comma-separated list of directories or archives).
150+
in `loader.properties` (comma-separated list of directories, archives, or directories
151+
within archives).
151152

152153

153154

@@ -280,7 +281,8 @@ the `Main-Class` attribute and leave out `Start-Class`.
280281
* `loader.home` is only the directory location of an additional properties file
281282
(overriding the default) as long as `loader.config.location` is not specified.
282283
* `loader.path` can contain directories (scanned recursively for jar and zip files),
283-
archive paths, or wildcard patterns (for the default JVM behavior).
284+
archive paths, a directory within an archive that is scanned for jar files (for
285+
example, `dependencies.jar!/lib`), or wildcard patterns (for the default JVM behavior).
284286
* `loader.path` (if empty) defaults to `BOOT-INF/lib` (meaning a local directory or a
285287
nested one if running from an archive). Because of this `PropertiesLauncher` behaves the
286288
same as `JarLauncher` when no additional configuration is provided.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
loader.path=jar:file:target/executable-props-lib-0.0.1.BUILD-SNAPSHOT-dependencies.jar/!BOOT-INF/lib
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<groupId>org.springframework.boot.launcher.it</groupId>
6+
<artifactId>executable-props-lib</artifactId>
7+
<version>0.0.1.BUILD-SNAPSHOT</version>
8+
<properties>
9+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
10+
</properties>
11+
<build>
12+
<plugins>
13+
<plugin>
14+
<groupId>org.apache.maven.plugins</groupId>
15+
<artifactId>maven-compiler-plugin</artifactId>
16+
<version>3.1</version>
17+
<configuration>
18+
<source>1.6</source>
19+
<target>1.6</target>
20+
</configuration>
21+
</plugin>
22+
<plugin>
23+
<groupId>org.apache.maven.plugins</groupId>
24+
<artifactId>maven-dependency-plugin</artifactId>
25+
<version>2.10</version>
26+
<executions>
27+
<execution>
28+
<id>unpack</id>
29+
<phase>prepare-package</phase>
30+
<goals>
31+
<goal>unpack</goal>
32+
</goals>
33+
<configuration>
34+
<artifactItems>
35+
<artifactItem>
36+
<groupId>@project.groupId@</groupId>
37+
<artifactId>@project.artifactId@</artifactId>
38+
<version>@project.version@</version>
39+
<type>jar</type>
40+
</artifactItem>
41+
</artifactItems>
42+
<outputDirectory>${project.build.directory}/app-assembly</outputDirectory>
43+
</configuration>
44+
</execution>
45+
<execution>
46+
<id>copy</id>
47+
<phase>prepare-package</phase>
48+
<goals>
49+
<goal>copy-dependencies</goal>
50+
</goals>
51+
<configuration>
52+
<outputDirectory>${project.build.directory}/dependencies-assembly/BOOT-INF/lib</outputDirectory>
53+
</configuration>
54+
</execution>
55+
</executions>
56+
</plugin>
57+
<plugin>
58+
<artifactId>maven-assembly-plugin</artifactId>
59+
<version>2.4</version>
60+
<executions>
61+
<execution>
62+
<id>app</id>
63+
<phase>package</phase>
64+
<goals>
65+
<goal>single</goal>
66+
</goals>
67+
<configuration>
68+
<descriptors>
69+
<descriptor>src/main/assembly/app.xml</descriptor>
70+
</descriptors>
71+
<archive>
72+
<manifest>
73+
<mainClass>org.springframework.boot.loader.PropertiesLauncher</mainClass>
74+
</manifest>
75+
<manifestEntries>
76+
<Start-Class>org.springframework.boot.launcher.it.props.EmbeddedJarStarter</Start-Class>
77+
</manifestEntries>
78+
</archive>
79+
</configuration>
80+
</execution>
81+
<execution>
82+
<id>depedendencies</id>
83+
<phase>package</phase>
84+
<goals>
85+
<goal>single</goal>
86+
</goals>
87+
<configuration>
88+
<descriptors>
89+
<descriptor>src/main/assembly/dependencies.xml</descriptor>
90+
</descriptors>
91+
</configuration>
92+
</execution>
93+
</executions>
94+
</plugin>
95+
</plugins>
96+
</build>
97+
<dependencies>
98+
<dependency>
99+
<groupId>org.springframework</groupId>
100+
<artifactId>spring-context</artifactId>
101+
<version>4.1.4.RELEASE</version>
102+
</dependency>
103+
</dependencies>
104+
</project>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<assembly
3+
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
6+
<id>app</id>
7+
<formats>
8+
<format>jar</format>
9+
</formats>
10+
<includeBaseDirectory>false</includeBaseDirectory>
11+
<dependencySets>
12+
<dependencySet>
13+
<useProjectArtifact/>
14+
<includes>
15+
<include>${project.groupId}:${project.artifactId}</include>
16+
</includes>
17+
<outputDirectory>BOOT-INF/classes</outputDirectory>
18+
<unpack>true</unpack>
19+
</dependencySet>
20+
</dependencySets>
21+
<fileSets>
22+
<fileSet>
23+
<directory>${project.build.directory}/app-assembly</directory>
24+
<outputDirectory>/</outputDirectory>
25+
</fileSet>
26+
</fileSets>
27+
</assembly>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<assembly
3+
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
6+
<id>dependencies</id>
7+
<formats>
8+
<format>jar</format>
9+
</formats>
10+
<includeBaseDirectory>false</includeBaseDirectory>
11+
<fileSets>
12+
<fileSet>
13+
<directory>${project.build.directory}/dependencies-assembly</directory>
14+
<outputDirectory>/</outputDirectory>
15+
</fileSet>
16+
</fileSets>
17+
</assembly>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.launcher.it.props;
18+
19+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
20+
21+
/**
22+
* Main class to start the embedded server.
23+
*
24+
* @author Dave Syer
25+
*/
26+
public final class EmbeddedJarStarter {
27+
28+
public static void main(String[] args) throws Exception {
29+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
30+
context.getBean(SpringConfiguration.class).run(args);
31+
context.close();
32+
}
33+
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.launcher.it.props;
18+
19+
import java.io.IOException;
20+
import java.util.Properties;
21+
22+
import javax.annotation.PostConstruct;
23+
24+
import org.springframework.context.annotation.ComponentScan;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.core.io.ClassPathResource;
27+
28+
/**
29+
* Spring configuration.
30+
*
31+
* @author Dave Syer
32+
*/
33+
@Configuration
34+
@ComponentScan
35+
public class SpringConfiguration {
36+
37+
private String message = "Jar";
38+
39+
@PostConstruct
40+
public void init() throws IOException {
41+
Properties props = new Properties();
42+
props.load(new ClassPathResource("application.properties").getInputStream());
43+
String value = props.getProperty("message");
44+
if (value!=null) {
45+
this.message = value;
46+
}
47+
48+
}
49+
50+
public void run(String... args) {
51+
System.err.println("Hello Embedded " + this.message + "!");
52+
}
53+
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
message: World
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
def jarfile = './target/executable-props-lib-0.0.1.BUILD-SNAPSHOT-app.jar'
2+
3+
new File("${basedir}/application.properties").delete()
4+
5+
String exec(String command) {
6+
def proc = command.execute([], basedir)
7+
proc.waitFor()
8+
proc.err.text
9+
}
10+
11+
String out = exec("java -jar ${jarfile}")
12+
assert out.contains('Hello Embedded World!'),
13+
'Using -jar my.jar should load dependencies from separate jar and use the application.properties from the jar\n' + out
14+
15+
out = exec("java -cp ${jarfile} org.springframework.boot.loader.PropertiesLauncher")
16+
assert out.contains('Hello Embedded World!'),
17+
'Using -cp my.jar with PropertiesLauncher should load dependencies from separate jar and use the application.properties from the jar\n' + out

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java

Lines changed: 15 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,10 @@
2121
import java.io.IOException;
2222
import java.io.InputStream;
2323
import java.net.HttpURLConnection;
24-
import java.net.MalformedURLException;
2524
import java.net.URL;
2625
import java.net.URLConnection;
2726
import java.util.ArrayList;
2827
import java.util.Collections;
29-
import java.util.Iterator;
3028
import java.util.List;
3129
import java.util.Properties;
3230
import java.util.jar.Manifest;
@@ -469,10 +467,10 @@ private List<Archive> getClassPathArchives(String path) throws Exception {
469467
debug("Adding classpath entries from archive " + archive.getUrl() + root);
470468
lib.add(archive);
471469
}
472-
Archive nested = getNestedArchive(root);
473-
if (nested != null) {
470+
List<Archive> nestedArchives = getNestedArchives(root);
471+
if (nestedArchives != null) {
474472
debug("Adding classpath entries from nested " + root);
475-
lib.add(nested);
473+
lib.addAll(nestedArchives);
476474
}
477475
return lib;
478476
}
@@ -490,19 +488,24 @@ private Archive getArchive(File file) throws IOException {
490488
return null;
491489
}
492490

493-
private Archive getNestedArchive(String root) throws Exception {
491+
private List<Archive> getNestedArchives(String root) throws Exception {
494492
if (root.startsWith("/")
495493
|| this.parent.getUrl().equals(this.home.toURI().toURL())) {
496494
// If home dir is same as parent archive, no need to add it twice.
497495
return null;
498496
}
499-
EntryFilter filter = new PrefixMatchingArchiveFilter(root);
500-
if (this.parent.getNestedArchives(filter).isEmpty()) {
501-
return null;
497+
Archive parent = this.parent;
498+
if (root.startsWith("jar:file:") && root.contains("!")) {
499+
int index = root.indexOf("!");
500+
String file = root.substring("jar:file:".length(), index);
501+
parent = new JarFileArchive(new File(file));
502+
root = root.substring(index + 1, root.length());
503+
while (root.startsWith("/")) {
504+
root = root.substring(1);
505+
}
502506
}
503-
// If there are more archives nested in this subdirectory (root) then create a new
504-
// virtual archive for them, and have it added to the classpath
505-
return new FilteredArchive(this.parent, filter);
507+
EntryFilter filter = new PrefixMatchingArchiveFilter(root);
508+
return parent.getNestedArchives(filter);
506509
}
507510

508511
private void addNestedEntries(List<Archive> lib) {
@@ -626,47 +629,4 @@ public boolean matches(Entry entry) {
626629

627630
}
628631

629-
/**
630-
* Decorator to apply an {@link Archive.EntryFilter} to an existing {@link Archive}.
631-
*/
632-
private static class FilteredArchive implements Archive {
633-
634-
private final Archive parent;
635-
636-
private final EntryFilter filter;
637-
638-
FilteredArchive(Archive parent, EntryFilter filter) {
639-
this.parent = parent;
640-
this.filter = filter;
641-
}
642-
643-
@Override
644-
public URL getUrl() throws MalformedURLException {
645-
return this.parent.getUrl();
646-
}
647-
648-
@Override
649-
public Manifest getManifest() throws IOException {
650-
return this.parent.getManifest();
651-
}
652-
653-
@Override
654-
public Iterator<Entry> iterator() {
655-
throw new UnsupportedOperationException();
656-
}
657-
658-
@Override
659-
public List<Archive> getNestedArchives(final EntryFilter filter)
660-
throws IOException {
661-
return this.parent.getNestedArchives(new EntryFilter() {
662-
@Override
663-
public boolean matches(Entry entry) {
664-
return FilteredArchive.this.filter.matches(entry)
665-
&& filter.matches(entry);
666-
}
667-
});
668-
}
669-
670-
}
671-
672632
}

0 commit comments

Comments
 (0)