Affects: Spring Framework 5.2.2
When trying to find a class from the class path using PathMatchingResourcePatternResolver
you will not find the class if your search pattern starts with classpath*:**/
and your are running your application with a single JAR containing a MANIFEST.MF with Class-path
that defines the class path.
For instance, com.vaadin
/flow-server
contains a class named Tag
in the com.vaadin.flow.component
package. If you do
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
for (Resource res : resolver.getResources("classpath*:**/component/Tag.class")) {
System.out.println(res);
}
and run the application through VS Code, which happens to use the manifest based class path approach, there are no matches. On the other hand if you run with a "normal" classpath, a URL for the Tag.class
will be printed
If you change the pattern to be classpath*:com/**/component/Tag.class
i.e. let it start with com/
, then both ways of running the application outputs the Tag.class
URL.
Example project based on Spring Boot here: https://github.com/Artur-/PathMatchingResourcePatternResolver-jarmanifest
Comment From: rstoyanchev
Thanks for the sample project.
PathMatchingResourcePatternResolver
delegates to java.lang.ClassLoader
which I would expect to be aware of the classpath regardless of how it is declared.
I don't use VS Code myself. I used the maven-jar-plugin to have a class-path entry added to the manifest. I confirmed the entry was in the manifest of the JAR in the target directory, and I ran it, and that worked as expected.
Perhaps you can further isolate the issue by just using ClassLoader#getResources
directly with those patterns, with VS Code and also without it (i.e. adding a class-path entry to the Manifest). The root cause may not be in the Spring Framework.
Comment From: Artur-
VS Code runs the project as
java -cp /var/folders/23/3rxqylv56gq4tkj5y6vj9j5r0000gn/T/cp_c5z8ekov909tfryjdiqu2xiv3.jar com.example.demo.DemoApplication
where the /var/folders/23/3rxqylv56gq4tkj5y6vj9j5r0000gn/T/cp_c5z8ekov909tfryjdiqu2xiv3.jar
file is autogenerated and contains only a META-INF/MANIFEST.MF
The manifest in turn contains:
Manifest-Version: 1.0
Class-Path: file:/projectpath/target/classes/ file:/.m2folder/repository/com/vaadin/flow-server/2.1.0/flow-server-2.1.0.jar file:/.m2folder/repository/com/vaadin/flow-push/2.1.0/flow-push-2.1.0.jar file:/.m2folder/repository/com/vaadin/external/atmosphere/atmosphere-runtime/2.4.30.slf4jvaadin1/atmosphere-runtime-2.4.30.slf4jvaadin1.jar file:/.m2folder/repository/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar file:/.m2folder/repository/com/vaadin/external/gwt/gwt-elemental/2.8.2.vaadin2/gwt-elemental-2.8.2.vaadin2.jar file:/.m2folder/repository/commons-fileupload/commons-fileupload/1.3.3/commons-fileupload-1.3.3.jar file:/.m2folder/repository/commons-io/commons-io/2.5/commons-io-2.5.jar file:/.m2folder/repository/org/jsoup/jsoup/1.12.1/jsoup-1.12.1.jar file:/.m2folder/repository/com/helger/ph-css/6.1.1/ph-css-6.1.1.jar file:/.m2folder/repository/com/helger/ph-commons/9.1.2/ph-commons-9.1.2.jar file:/.m2folder/repository/net/bytebuddy/byte-buddy/1.10.6/byte-buddy-1.10.6.jar file:/.m2folder/repository/com/vaadin/external/gentyref/1.2.0.vaadin1/gentyref-1.2.0.vaadin1.jar file:/.m2folder/repository/org/springframework/boot/spring-boot-starter/2.2.4.RELEASE/spring-boot-starter-2.2.4.RELEASE.jar file:/.m2folder/repository/org/springframework/boot/spring-boot/2.2.4.RELEASE/spring-boot-2.2.4.RELEASE.jar file:/.m2folder/repository/org/springframework/spring-context/5.2.3.RELEASE/spring-context-5.2.3.RELEASE.jar file:/.m2folder/repository/org/springframework/spring-aop/5.2.3.RELEASE/spring-aop-5.2.3.RELEASE.jar file:/.m2folder/repository/org/springframework/spring-beans/5.2.3.RELEASE/spring-beans-5.2.3.RELEASE.jar file:/.m2folder/repository/org/springframework/spring-expression/5.2.3.RELEASE/spring-expression-5.2.3.RELEASE.jar file:/.m2folder/repository/org/springframework/boot/spring-boot-autoconfigure/2.2.4.RELEASE/spring-boot-autoconfigure-2.2.4.RELEASE.jar file:/.m2folder/repository/org/springframework/boot/spring-boot-starter-logging/2.2.4.RELEASE/spring-boot-starter-logging-2.2.4.RELEASE.jar file:/.m2folder/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar file:/.m2folder/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar file:/.m2folder/repository/org/apache/logging/log4j/log4j-to-slf4j/2.12.1/log4j-to-slf4j-2.12.1.jar file:/.m2folder/repository/org/apache/logging/log4j/log4j-api/2.12.1/log4j-api-2.12.1.jar file:/.m2folder/repository/org/slf4j/jul-to-slf4j/1.7.30/jul-to-slf4j-1.7.30.jar file:/.m2folder/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar file:/.m2folder/repository/org/springframework/spring-core/5.2.3.RELEASE/spring-core-5.2.3.RELEASE.jar file:/.m2folder/repository/org/springframework/spring-jcl/5.2.3.RELEASE/spring-jcl-5.2.3.RELEASE.jar file:/.m2folder/repository/org/yaml/snakeyaml/1.25/snakeyaml-1.25.jar
with proper line breaks an projectpath
+ .m2folder
replaced with the appropriate for your environment.
Comment From: Artur-
Now that I read javadocs more carefully, is this related to
WARNING: Note that "classpath:" when combined with Ant-style patterns will only work reliably with at least one root directory before the pattern starts, unless the actual target files reside in the file system. This means that a pattern like "classpath:*.xml" will not retrieve files from the root of jar files but rather only from the root of expanded directories. This originates from a limitation in the JDK's ClassLoader.getResources() method which only returns file system locations for a passed-in empty String (indicating potential roots to search). This ResourcePatternResolver implementation is trying to mitigate the jar root lookup limitation through URLClassLoader introspection and "java.class.path" manifest evaluation; however, without portability guarantees.
?
Comment From: rstoyanchev
I don't think so since the target file is not at the very top.
It would be helpful if you could debug this further in your environment as I suggested above. You could replace the use of PathMATchingResourcePatternResolver
and try to look up the root URLs in each scenario directly, i.e. getClass().getClassLoader().getResources("")
.
Comment From: Artur-
When running using java -jar target/demo-0.0.1-SNAPSHOT.jar
then
getClass().getClassLoader().getResources("")
returns
jar:file:/.../target/demo-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/
jar:file:/.../target/demo-0.0.1-SNAPSHOT.jar!/BOOT-INF/lib/flow-server-2.1.0.jar!/
jar:file:/.../target/demo-0.0.1-SNAPSHOT.jar!/BOOT-INF/lib/flow-push-2.1.0.jar!/
...
+ spring boot jars and more
when running through VS Code, the same code returns
file:/.../target/classes/
and nothing else
Comment From: rstoyanchev
Could a different JDK be in use in each case? Or could it be something in the way VS Code sets up that class-path entry? As mentioned above, with the maven-jar-plugin to add the class-path entry I could not reproduce the issue. Not sure what the difference is but I wonder if you get the same result in your environment with maven-jar-plugin?
Either way this seems to demonstrate the issue without the Spring Framework involved. It looks suitable to file with VS Code as a potential issue especially if you get the same outcome as me with the maven-jar-plugin.
Comment From: Artur-
Thanks, I had a friend test which also could not reproduce it.
Seems like the problem is in my JDK in use, which happens to be
openjdk version "1.8.0_222"
OpenJDK Runtime Environment Corretto-8.222.10.1 (build 1.8.0_222-b10)
OpenJDK 64-Bit Server VM Corretto-8.222.10.1 (build 25.222-b10, mixed mode)
With this I see the described behavior.
Changing to
openjdk version "11.0.4" 2019-07-16 LTS
OpenJDK Runtime Environment Corretto-11.0.4.11.1 (build 11.0.4+11-LTS)
OpenJDK 64-Bit Server VM Corretto-11.0.4.11.1 (build 11.0.4+11-LTS, mixed mode)
makes the lookup work correctly.
Thanks for the help!
Comment From: cnaj
I too have observed the behavior, which seems unrelated to the JDK used. Please see my demo application at https://github.com/cnaj/path-matching-resource-pattern-resolver-demo, which shows the issue with both Java 8 and Java 11. I'm working on a Maven demo, but for now you can most easily see the behavior in IntelliJ IDEA.
I don't know what VS Code does, but since Java 9 you have the possibility to pass command line arguments in a regular text file (a.k.a. "@argFiles"). If that't what VS Code does, this might explain why @Artur- has seen the behavior disappear with Java 11.
Note: I've come across this issue in a Bazel project with many dependencies, which turned out to be a blocker for using Bazel as long as it doesn't support Java 9 argfiles.
Thank you for looking into this!
Comment From: cnaj
Added demo scripts clearly showing the behavior to https://github.com/cnaj/path-matching-resource-pattern-resolver-demo
Comment From: cnaj
@rstoyanchev Do you have the chance to look at my demo, or should I open a new issue?
Comment From: arxanas
@cnaj Thank you very much for your detailed explanation of the problem in your linked repository. I'm also migrating a large codebase to Bazel, and I had been trying to track down this problem for a while.
Comment From: cnaj
Hello @arxanas, I’m not sure if the maintainers react on a closed bug. Would you like to open a new one? I unfortunately don’t have the time right now. You can of course use and copy the information from my repository.