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 :)