Skip to content

Add support for loading META-INF/resources from jars referenced via a Class-Path manifest entry #9513

@torerefsnes

Description

@torerefsnes

Spring Boot 1.4.7

Background:
Due to path length restrictions on Windows, IntelliJ has a "dynamic classloading" feature. When enabled, it creates a file called "classpath.jar" and puts the classpath of the application into the MANIFEST.MF file,

Problem description:
Spring Boot normally adds "resource jars", i.e. those containing a "META-INF/resources" folder to the Tomcat context, making static resources available to the Tomcat servlet engine. This is done here:

TomcatEmbeddedServletContainerFactory:

context.addLifecycleListener(new LifecycleListener() {

@Override
public void lifecycleEvent(LifecycleEvent event) {
	if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
		TomcatResources.get(context)
				.addResourceJars(getUrlsOfJarsWithMetaInfResources());
	}
}
});

In AbstractEmbeddedServletContainerFactory.getUrlsOfJarsWithMetaInfResources(), we find this code:

ClassLoader classLoader = getClass().getClassLoader();
List<URL> staticResourceUrls = new ArrayList<URL>();
if (classLoader instanceof URLClassLoader) {
	for (URL url : ((URLClassLoader) classLoader).getURLs()) {
[...]

This works as long as the ClassLoader used was initialized with a specific classpath. If the classpath refers to a jar that has a Class-Path entry in its manifest, Spring Boot does not find those jars.

Workaround:
I solved this issue locally by extending the TomcatEmbeddedServletContainerFactory, overriding the getUrlsOfJarsWithMetaInfResources() so that it also considers the entries in the Class-Path attribute of the jar file's manifest (if it exists).

So - in addition to the URLs returned by URLClassLoader.getURLs(), we add these to the set:

    private URL[] getJarUrlsFromManifests(ClassLoader cl) {
        try {
            Set<URL> urlSet = new LinkedHashSet<>();

            URL url = cl.getResource("META-INF/MANIFEST.MF");

            if (url != null) {
                Manifest manifest = new Manifest(url.openStream());

                String classPath = manifest.getMainAttributes().getValue("Class-Path");

                if (classPath != null) {
                    for (String urlStr : classPath.split(" ")) {
                        try {
                            urlSet.add(new URL(urlStr));
                        } catch (MalformedURLException ex) {
                            throw new AssertionError();
                        }
                    }
                }
            }

            return urlSet.toArray(new URL[urlSet.size()]);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

I think Spring Boot should support this too.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions