ClassLoader cannot scan the desired class only when a specific maven task is executed

TL;DR --maven-surefire-plugin puts a lot of jar files in classpath into surefirebooter ~~~ .jar. --You can get the internal URL list by putting ClasspathHelper.forManifest () after ClasspathHelper.forClassLoader ().

Details

I wrote the following code to scan a subclass with a particular interface at runtime.

    List<ClassLoader> classLoadersList = new LinkedList<>();
    classLoadersList.add(ClasspathHelper.contextClassLoader());
    classLoadersList.add(ClasspathHelper.staticClassLoader());

    Reflections reflections = new Reflections(new ConfigurationBuilder()
        .setScanners(new SubTypesScanner(false), new ResourcesScanner())
        .setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
        // Should append suffix \..* since filter is applied to package+class name.
        .filterInputsBy(new FilterBuilder().include(packageRegex + "\\..*")));

    // Get subtypes you want.
    reflections.getSubTypesOf(YourInterface.class).stream()...

It worked fine while testing with the GUI in the IDE (IntelliJ IDEA), but sometimes I couldn't find the class I wanted only when I did mvn verify. So I tried to output the list of classpath.

    for (URL url : ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0]))) {
      System.out.println(String.format("%s", url.toString()));
    }

It should output the jar of the library you are using and the folder path to the module in the project.

――What you really want

file:/C:/Users/.../build/classes/java/test
file:/C:/Users/.../build/libs/mylibrary.jar
file:/C:/Users/.../.m2/repository/javax/servlet/javax.servlet-api/3.1.0/javax.servlet-api-3.1.0.jar
...
file:/C:/Program%20Files/Amazon%20Corretto/jdk1.8.0_265/jre/lib/ext/access-bridge-64.jar
file:/C:/Program%20Files/Amazon%20Corretto/jdk1.8.0_265/jre/lib/ext/cldrdata.jar
file:/C:/Program%20Files/Amazon%20Corretto/jdk1.8.0_265/jre/lib/ext/dnsns.jar
...

However, when I did mvn verify, I got the following result.

--Results of mvn verify

file:/C:/Users/.../target/surefire/surefirebooter4604658542841964121.jar
file:/C:/Users/.../.m2/repository/org/jacoco/org.jacoco.agent/0.8.2/org.jacoco.agent-0.8.2-runtime.jar
file:/C:/Program%20Files/Amazon%20Corretto/jdk1.8.0_265/jre/lib/ext/access-bridge-64.jar
file:/C:/Program%20Files/Amazon%20Corretto/jdk1.8.0_265/jre/lib/ext/cldrdata.jar
file:/C:/Program%20Files/Amazon%20Corretto/jdk1.8.0_265/jre/lib/ext/dnsns.jar
...

This is because maven-surefire-plugin puts a lot of jars together in surefirebooter4604658542841964121.jar. The reason for summarizing is that it may not be possible to specify the classpath for all jars when executing the java command because the length of the command that can be executed is limited due to the difference in OS and command line environment. So, the contents of this surefirebooter jar is a manifest file instead of the actual jar unless the options are specified. image.png

For manifest.mf, you can expand and receive the URL in manifest.mf by usingClasspathHelper.forManifest ()instead ofClasspathHelper.forClassLoader (). Also, ClasspathHelper.forManifest () will return the URL of the jar as is, even if the passed one is not a manifest file. So you can get the URLs of all the jars by overlaying forClassLoader () and forManifest () as follows, without having to determine if it's a manifest file.

    final Collection<URL> effectiveClassUrls =
        // Resolve manifest in Surefirebooter jar in classpath.
        ClasspathHelper.forManifest(
            ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])));

Ultimately, the process of scanning for the required subclasses is as follows:

    List<ClassLoader> classLoadersList = new LinkedList<>();
    classLoadersList.add(ClasspathHelper.contextClassLoader());
    classLoadersList.add(ClasspathHelper.staticClassLoader());

    final Collection<URL> effectiveClassUrls =
        // Resolve manifest in Surefirebooter jar in classpath.
        ClasspathHelper.forManifest(
            ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])));

    Reflections reflections = new Reflections(new ConfigurationBuilder()
        .setScanners(new SubTypesScanner(false), new ResourcesScanner())
        .setUrls(effectiveClassUrls)
        // Should append suffix \..* since filter is applied to package+class name.
        .filterInputsBy(new FilterBuilder().include(packageRegex + "\\..*")));

    // Get subtypes you want.
    reflections.getSubTypesOf(YourInterface.class).stream()...

Supplement

Please note that even if maven-surefire-plugin is not explicitly added to pom.xml, it may still be defined in the dependent libraries. This time in my case I was using maven-failsafe-plugin, in which surefire was also enabled.

Recommended Posts

ClassLoader cannot scan the desired class only when a specific maven task is executed
When the bean class name is duplicated
What is the difference between a class and a struct? ?? ??
How to perform a specific process when the back button is pressed in Android Fragment
When a Java file created with the Atom editor is garbled when executed at the command prompt