Description
Spring Boot 1.5.4
Scenario:
A Spring Boot application is packaged as a "fat jar". The jar then has the following structure:
BOOT-INF
classes
[...]
lib
comp1.jar
[...]
META-INF
resources
index.jsp
[...]
org
springframework
[...]
A number of static resources to be served by Tomcat are included in the META-INF/resources folder.
I find that the static resources are not added to the Tomcat servlet context, and cannot be found when run in "fat jar" mode. When not run as a "fat jar", it works.
Cause:
The reason for this is the way AbstractEmbeddedServletContainerFactory.getUrlsOfJarsWithMetaInfResources() works:
ClassLoader classLoader = getClass().getClassLoader();
List<URL> staticResourceUrls = new ArrayList<URL>();
if (classLoader instanceof URLClassLoader) {
for (URL url : ((URLClassLoader) classLoader).getURLs()) {
[...]
The classloader hierarchy (with the results returned by getURLs()) is as follows when the application in run as a "fat jar":
sun.misc.Launcher$ExtClassLoader:
[...] misc system jars
sun.misc.Launcher$AppClassLoader:
**URL:file:/path/to/fatjar.jar**
org.springframework.boot.loader.LaunchedURLClassLoader:
URL:jar:file:/path/to/fatjar.jar!/BOOT-INF/classes!/
URL:jar:file:/path/to/fatjar.jar!/BOOT-INF/lib!/comp1.jar!/
getClass() returns the LaunchedURLClassLoader, which only includes its embedded jars in getURLs(). Therefore, none of the resources in the main jar are found - only those embedded in the "fat jar". It works when not run as a fat jar, since the AppClassLoader returns all the URLs and paths to resources.
Suggested solution:
A solution that I have tested OK is to traverse the parent ClassLoaders too, scanning those for jars containing a META-INF/resources folder:
private URL[] getURLs(URLClassLoader classLoader) {
Set<URL> urlSet = new LinkedHashSet<>();
ClassLoader c = classLoader;
while (c != null) {
if (c instanceof URLClassLoader) {
Collections.addAll(urlSet, ((URLClassLoader) c).getURLs());
}
c = c.getParent();
}
return urlSet.toArray(new URL[urlSet.size()]);
}
ClassLoader classLoader = getClass().getClassLoader();
List<URL> staticResourceUrls = new ArrayList<>();
if (classLoader instanceof URLClassLoader) {
-> for (URL url : getURLs((URLClassLoader) classLoader)) {