Recently, I had some trouble loading classpath resources. During local development, I can obtain the resources under classpath through classloader. And If this resource is a folder, I can get sub-files/ sub-directories through the File#listfiles method. But this doesn't work when the program is packaged in a jar package, the file in a jar is not a file, and the folder is not a folder. In this case, they are all JarEntry.

So I found out the PathMatchingResourcePatternResolver, which I hoped would solve my problem, but it couldn't.

The problem is described as follows:

First, we create a new PathMatchingResourcePatternResolver instance:

package com.xxx;

public class Main {
  public static void main(String[] args) {
    ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  }
}

I want to get the resources under classpath, such as all sub-resources under 'org/springframework/core/io/support/',

resolver.getResources("classpath:org/springframework/core/io/support/*")

I specified the prefix "classpath:" just as I wanted to get resources under classpath. The statement returns an Array with component type ClassPathResource. That is exactly what I want. And the class name ClassPathResource is just so NICE.

OK, We just tried to get the resources inside a jar. Next, We are going to get something in expanded directory(file system). My program has a package calledcom.xxx. Next, let's try to obtain the resources under "com/xxx":

resolver.getResources("classpath:com/xxx");

This time, take a closer look. It returns an Array with concrete component type FileSystemResource. I am confused now. I specified the prefix "classpath:", instead of "file:///", so why give me a FileSystemResource?

I am expecting the perfect ClassPathResource because I need its getPath method to do other things.

Finally, I am thinking about that this may be a defect in implementation, and should be resolve. What do you guys think?

Comment From: justmehyp

I found the source code which treat jar resources differently from file resources. it's PathMatchingResourcePatternResolver#findPathMatchingResources. Related code snippet is following:

else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
    result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
}
else {
    result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}

First, within the doFindPathMatchingJarResources method, it uses rootDirResource.createRelative(relativePath) to create an object, If the parent resource is a ClassPathResource, then the child resource is also ClassPathResource.

Then, let's look at the doFindPathMatchingFileResources method. The doFindMatchingFileSystemResources method is called inside the method, so we then drill into the doFindMatchingFileSystemResources method. This method uses new FileSystemResource(file) to create sub-resources, which should not be: No matter what type of the current resource is, the sub-resource will always become a FileSystemResource.

In fact, it is not difficult to fix this problem. I submitted a PR to solve the problem: 25624

Comment From: Martin-Luft

Since 5.3.0 my package scanner is broken, which uses the ResourcePatternResolver to find classpath resources inside a JAR. I think this is related to this issue.

Comment From: Martin-Luft

@rstoyanchev why do you closed this issue? The bug is still present in Spring 5.3.13 :(

Comment From: rstoyanchev

@Martin-Wegner it is closed as superseded by PR#25624. If both an issue and a PR are opened, we keep only one open. They are linked and there is no need to have both.

Comment From: Martin-Luft

@rstoyanchev thanks for this explanation :)