-
Notifications
You must be signed in to change notification settings - Fork 38.8k
Description
During acceptance of a batch using Spring Framework 6.2.9, while the batch is loading a resource file from classpath with ClassPathResource, this error occurs sometimes :
java.io.FileNotFoundException: class path resource [[mailsTemplates/myTemplate.html] cannot be opened because it does not exist
at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:215)
at org.springframework.core.io.Resource.getContentAsString(Resource.java:164)
The error does not occur during functional tests. In production, the resource file is inside the application jar (nested into spring boot executable jar) at the good path. Since I did not provide a class loader to my ClassPathResource, I guess it is a class loader issue : if the constructor ClassPathResource#ClassPathResource(java.lang.String) is not executed with the "good class loader" in the thread context, the error can happen.
I fixed my error providing a "good class loader" at the constructor for the ClassPathResource instance.
I had a look to the method which causes the error : the FileNotFoundException is raised because in the method ClassPathResource#getInputStream when the InputStream returned by this.classLoader#getResourceAsStream is null : :
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.absolutePath);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.absolutePath);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}The resource location could be resolved by the system class loader if it was called. But the statement is = ClassLoader.getSystemResourceAsStream(this.absolutePath) can only be called if the ClassPathResource instance is created by new ClassPathResource(path, (Class<?>) null). It is not called when the ClassPathResource instance is created by new ClassPathResource(path). So if it has not impact elsewhere, I suggest that when the ClassPathResource object is instancied from ClassPathResource(String) and when, in getInputStream method, InputStream got from this.classLoader.getResourceAsStream(this.absolutePath) is null, the InputStream resolution should fallback to the system class loader. Note that to be consistent, the suggestion should also apply to ClassPathResource#resolveURL.
- To reproduce my initial bug : run the test org.springframework.core.io.ClassPathResourceTests#testResourceLocation from my fork : the test fails with the FileNotFoundException
- if you replace
var classpathResource = new ClassPathResource("scanned-resources/resource#test1.txt")withvar classpathResource = new ClassPathResource("scanned-resources/resource#test1.txt", (Class<?>) null)in the test, it succeeds - if you apply the suggestion to
ClassPathResource#getInputStream(fallback to system class loader when this.classLoader.getResourceAsStream returns null), the test succeeds also.
- if you replace