Skip to content

Commit c829d60

Browse files
committed
New feature: thin.libs to append to classpath
Fixes #171, #15
1 parent 27fe148 commit c829d60

File tree

6 files changed

+95
-19
lines changed

6 files changed

+95
-19
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ You can set a variety of options on the command line or with system properties (
319319
| `thin.force` | false | Force dependency resolution to happen, even if dependencies have been computed, and marked as "computed" in `thin.properties`. |
320320
| `thin.classpath` | false | Only print the classpath. Don't run the main class. Two formats are supported: "path" and "properties". For backwards compatibility "true" or empty are equivalent to "path". |
321321
| `thin.root` | `${user.home}/.m2` | The location of the local jar cache, laid out as a maven repository. The launcher creates a new directory here called "repository" if it doesn't exist. |
322+
| `thin.libs` | `<empty>` | Additional classpath entries to append at runtime in the same form as you would use in `java -classpath ...`. If this property is defined then unresolved dependencies will be ignored when the classpath is computed, possibly leading to runtime class not found exceptions. |
322323
| `thin.archive` | the same as the target archive | The archive to launch. Can be used to launch a JAR file that was build with a different version of the thin launcher, for instance, or a fat jar built by Spring Boot without the thin launcher. |
323324
| `thin.parent` | `<empty>` | A parent archive to use for dependency management and common classpath entries. If you run two apps with the same parent, they will have a classpath that is the same, reading from left to right, until they actually differ. |
324325
| `thin.location` | `file:.,classpath:/` | The path to directory containing thin properties files (as per `thin.name`), as a comma-separated list of resource locations (directories). These locations plus the same paths relative /META-INF will be searched. |

launcher/src/main/java/org/springframework/boot/loader/thin/ArchiveUtils.java

+31-17
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727

2828
import org.eclipse.aether.artifact.DefaultArtifact;
2929
import org.eclipse.aether.graph.Dependency;
30-
3130
import org.springframework.boot.loader.archive.Archive;
3231
import org.springframework.boot.loader.archive.ExplodedArchive;
3332
import org.springframework.boot.loader.archive.JarFileArchive;
@@ -53,17 +52,15 @@ public static Archive getArchive(String path) {
5352
}
5453
try {
5554
return new JarFileArchive(new JarFile(file));
56-
}
57-
catch (IOException e) {
55+
} catch (IOException e) {
5856
throw new IllegalStateException("Cannot create JAR archive: " + file, e);
5957
}
6058
}
6159

6260
public static File getArchiveRoot(Archive archive) {
6361
try {
6462
return new File(jarFile(archive.getUrl()).toURI());
65-
}
66-
catch (Exception e) {
63+
} catch (Exception e) {
6764
throw new IllegalStateException("Cannot locate JAR archive: " + archive, e);
6865
}
6966
}
@@ -82,19 +79,16 @@ public static String findMainClass(Archive archive) {
8279
return mainClass;
8380
}
8481
}
85-
}
86-
catch (Exception e) {
82+
} catch (Exception e) {
8783
}
8884
try {
8985
File root = getArchiveRoot(archive);
9086
if (archive instanceof ExplodedArchive) {
9187
return MainClassFinder.findSingleMainClass(root);
92-
}
93-
else {
88+
} else {
9489
return MainClassFinder.findSingleMainClass(new JarFile(root), "");
9590
}
96-
}
97-
catch (Exception e) {
91+
} catch (Exception e) {
9892
throw new IllegalStateException("Cannot locate main class in " + archive, e);
9993
}
10094
}
@@ -104,8 +98,7 @@ private static URI findArchive(String path) {
10498
if (archive != null) {
10599
try {
106100
return jarFile(archive.toURL()).toURI();
107-
}
108-
catch (Exception e) {
101+
} catch (Exception e) {
109102
throw new IllegalStateException("Cannot create URI for " + archive);
110103
}
111104
}
@@ -148,8 +141,7 @@ private static URL jarFile(URL url) {
148141
}
149142
try {
150143
url = new URL(path);
151-
}
152-
catch (MalformedURLException e) {
144+
} catch (MalformedURLException e) {
153145
throw new IllegalStateException("Bad URL for jar file: " + path, e);
154146
}
155147
}
@@ -165,8 +157,7 @@ public static List<URL> nestedClasses(Archive archive, String... paths) {
165157
extras.add(classes.getURL());
166158
}
167159
}
168-
}
169-
catch (Exception e) {
160+
} catch (Exception e) {
170161
throw new IllegalStateException("Cannot create urls for resources", e);
171162
}
172163
return extras;
@@ -189,4 +180,27 @@ private static URL[] locateFiles(URL[] urls) {
189180
return urls;
190181
}
191182

183+
public static List<Archive> getArchives(String path) {
184+
List<Archive> list = new ArrayList<>();
185+
for (String element : path.split(File.pathSeparator)) {
186+
if (element.endsWith("*")) {
187+
File dir = new File(element.substring(0, element.length() - 1));
188+
if (dir.isDirectory()) {
189+
for (File file : dir.listFiles()) {
190+
if (file.getName().endsWith(".jar")) {
191+
try {
192+
list.add(getArchive(file.getCanonicalPath()));
193+
} catch (IOException e) {
194+
// ignore
195+
}
196+
}
197+
}
198+
}
199+
} else {
200+
list.add(getArchive(element));
201+
}
202+
}
203+
return list;
204+
}
205+
192206
}

launcher/src/main/java/org/springframework/boot/loader/thin/DependencyResolver.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ public List<Dependency> dependencies(final Resource resource,
214214
DependencyResolver.globals = null;
215215
DependencyResolutionResult dependencies = result
216216
.getDependencyResolutionResult();
217-
if (!dependencies.getUnresolvedDependencies().isEmpty()) {
217+
if (!dependencies.getUnresolvedDependencies().isEmpty() &&
218+
properties.getProperty(ThinJarLauncher.THIN_LIBS, "").length()!=0) {
218219
StringBuilder builder = new StringBuilder();
219220
for (Dependency dependency : dependencies
220221
.getUnresolvedDependencies()) {

launcher/src/main/java/org/springframework/boot/loader/thin/ThinJarLauncher.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.net.URL;
2121
import java.security.AccessControlException;
2222
import java.util.ArrayList;
23+
import java.util.Collections;
2324
import java.util.HashMap;
2425
import java.util.Iterator;
2526
import java.util.List;
@@ -128,14 +129,22 @@ public class ThinJarLauncher extends ExecutableArchiveLauncher {
128129

129130
/**
130131
* Flag to say that classloader parent should be the boot loader, not the system class
131-
* loader. Default true;
132+
* loader. Default true.
132133
*/
133134
public static final String THIN_PARENT_BOOT = "thin.parent.boot";
134135

136+
/**
137+
* Additional path elements to append to classpath, with OS-specific path separator. Also
138+
* accepts wildcards on a directory path (like java classpath). Default empty.
139+
*/
140+
public static final String THIN_LIBS = "thin.libs";
141+
135142
private StandardEnvironment environment = new StandardEnvironment();
136143

137144
private boolean debug;
138145

146+
private List<Archive> libs = new ArrayList<>();
147+
139148
public static void main(String[] args) throws Exception {
140149
LogUtils.setLogLevel(Level.OFF);
141150
new ThinJarLauncher(args).launch(args);
@@ -173,6 +182,7 @@ protected void launch(String[] args) throws Exception {
173182
LogUtils.setLogLevel(Level.INFO);
174183
}
175184
}
185+
this.libs.addAll(ArchiveUtils.getArchives(environment.resolvePlaceholders("${thin.libs:}")));
176186
if (classpath) {
177187
List<Archive> archives = getClassPathArchives();
178188
System.out.println(classpath(archives));
@@ -370,8 +380,12 @@ private List<Archive> getClassPathArchives(String root) throws Exception {
370380
profiles);
371381
long t1 = System.currentTimeMillis();
372382
if (log.isInfoEnabled()) {
383+
if (!this.libs.isEmpty()) {
384+
log.info("Adding libraries: " + this.libs);
385+
}
373386
log.info("Dependencies resolved in: " + (t1 - t0) + "ms");
374387
}
388+
archives.addAll(this.libs);
375389
return archives;
376390
}
377391

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2023 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+
* https://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+
package org.springframework.boot.loader.thin;
17+
18+
import org.junit.jupiter.api.Test;
19+
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
22+
import java.io.File;
23+
24+
public class ArchiveUtilsTests {
25+
26+
@Test
27+
public void resolveAll() throws Exception {
28+
assertThat(ArchiveUtils.getArchives("src/test/resources/*").size()).isEqualTo(2);
29+
}
30+
31+
@Test
32+
public void resolveSome() throws Exception {
33+
assertThat(ArchiveUtils.getArchives("src/test/resources/app-with-web-and-cloud-config.jar" + File.pathSeparator
34+
+ "src/test/resources/app-with-web-in-lib-properties.jar").size()).isEqualTo(2);
35+
}
36+
37+
}

launcher/src/test/java/org/springframework/boot/loader/thin/DependencyResolverTests.java

+9
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,15 @@ public void authentication() throws Exception {
339339
assertThat(dependencies.size()).isGreaterThan(16);
340340
}
341341

342+
@Test
343+
public void unresolvedButLibsProvided() throws Exception {
344+
Resource resource = new ClassPathResource("apps/missing/pom.xml");
345+
Properties properties = new Properties();
346+
properties.setProperty("thin.libs", "no/such/file");
347+
List<Dependency> dependencies = resolver.dependencies(resource, properties);
348+
assertThat(dependencies.size()).isGreaterThan(0);
349+
}
350+
342351
static Condition<Dependency> version(final String version) {
343352
return new Condition<Dependency>("artifact matches " + version) {
344353
@Override

0 commit comments

Comments
 (0)