[jira] [Commented] (MNG-7855) Dependencies wrongly put on class-path rather than module-path
[ https://issues.apache.org/jira/browse/MNG-7855?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel=17759233#comment-17759233 ] Martin Desruisseaux commented on MNG-7855: -- Another area where being on the class-path versus module-path makes a big difference is when loading other JAR files dynamically (after JVM startup). In a class-path world, we use {{URLClassLoader}}. In a module-path world, {{URLClassLoader}} does not work anymore and we have to use {{ModuleLayer}} instead. The two approaches are very different. For making {{ServiceLoader}} compatible with both class-path and module-path, duplicating the {{module-info}} information into {{META-INF/services/}} is problematic but still sometime doable (not always, because the two approaches are not fully equivalent). However duplicating a dynamic JAR loading capability with one framework based on {{ModuleLayer}} and another framework based on {{ClassLoader}} is much more difficult. For avoiding such bug prone situations, it is critical to be able to put a library on the module-path no matter if the client application is modularized or not. > Dependencies wrongly put on class-path rather than module-path > -- > > Key: MNG-7855 > URL: https://issues.apache.org/jira/browse/MNG-7855 > Project: Maven > Issue Type: Bug > Components: Dependencies >Affects Versions: 3.8.6, 4.0.0-alpha-7 >Reporter: Martin Desruisseaux >Priority: Blocker > Attachments: MavenModulepathBug.zip > > > When invoking Java tools such as {{java}} or {{javac}}, the project > dependencies can be declared either in a {{\--class-path}} or > {{\--module-path}} option. Maven choose automatically the module-path if all > the following conditions are true: > # the dependency is modularized (i.e. contains a {{module-info.class}} file > or an {{Automatic-Module-Name}} attribute in {{MANIFEST.MF}}), and > # the project using the dependency is itself modularized. > Condition #1 is fine, but #2 is problematic. The fact that a dependency is > declared on the class-path rather than the module-path changes the way that > {{java.util.ServiceLoader}} discovers the provided services. > * If the dependency is on the class-path, {{ServiceLoader}} scans the content > of {{META-INF/services}} directory. > * If the dependency is on the module-path, {{ServiceLoader}} uses the > declarations in {{module-info.class}}. > Even if condition #2 is false (i.e. a project is not modularized), > modularized dependencies still need to be declared on the module-path _for > allowing the dependency to discover its own services, or the services of a > transitive modularized dependency_. If a modularized dependency is put on the > class-path instead, it has consequence not only for the project using that > dependency, *but also for the dependency itself, which become unable to use > its own {{module-info.class}}*. > h1. Demonstration > The attached test case contains two Maven modules, named {{service}} and > {{client}}. The first Maven module declares a dummy services with 4 > providers, named A, B, C and D. Providers A and D are declared in > {{module-info}}. Providers B and C are declared in {{META-INF/services}}. A > {{ShowMyServices}} class lists the services discovered by > {{java.util.ServiceLoader}}. > The second Maven module has only a main method invoking {{ShowMyServices}}. > This second module intentionally has no {{module-info.java}} file. The use > case is a big module that we cannot modularize immediately (because > modularization brings stronger encapsulation, which requires significant > changes in the project to modularize), but still want to use modularized > dependencies. The test case can be run with {{mvn install}}. During test > execution, the following is printed: > {noformat} > Running test.client.MainTest > Start searching for services... > Provider B declared in META-INF. > Provider C declared in META-INF. > Done. > The dependency has been loaded as an unnamed module. > Consequently its `module-info` file has been ignored, > and the `META-INF/services` directory is used instead. > {noformat} > The above test demonstrates that {{module-info}} has been ignored in the > context of JUnit test execution. The same behaviour happens also with {{mvn > exec:java}} executed in the {{client}} sub-directory. > h2. Expected behaviour > The Maven behaviour can be reproduced on the command-line as below (Linux > convention). This command put everything on the class-path: > {code:bash} > java --class-path service/target/service-1.0.jar:client/target/client-1.0.jar > test.client.Main > {code} > The expected behaviour can be reproduced with the following command-line. > This command put the modularized dependency on the module-path while keeping >
[jira] [Commented] (MNG-7855) Dependencies wrongly put on class-path rather than module-path
[ https://issues.apache.org/jira/browse/MNG-7855?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel=17748556#comment-17748556 ] Martin Desruisseaux commented on MNG-7855: -- Java code for generating workarounds has been added in the [test case GitHub repository|https://github.com/Geomatys/MavenModulepathBug], in the {{workaround}} sub-directory. That code parses de {{module-info.class}} entries of all specified JAR files and generates a {{META-INF/services/}} directory with all service providers found. If a service provider declares a public static {{provider()}} method, then the program also generates a {{java}} sub-directory with Java code for wrappers. Those wrappers redirect all methods of the service interface to the same methods of the provider obtained by a call to the {{provider()}} static method. This workaround is of course unsatisfying, especially the wrappers which will not work in all cases. > Dependencies wrongly put on class-path rather than module-path > -- > > Key: MNG-7855 > URL: https://issues.apache.org/jira/browse/MNG-7855 > Project: Maven > Issue Type: Bug > Components: Dependencies >Affects Versions: 3.8.6, 4.0.0-alpha-7 >Reporter: Martin Desruisseaux >Priority: Blocker > Attachments: MavenModulepathBug.zip > > > When invoking Java tools such as {{java}} or {{javac}}, the project > dependencies can be declared either in a {{\--class-path}} or > {{\--module-path}} option. Maven choose automatically the module-path if all > the following conditions are true: > # the dependency is modularized (i.e. contains a {{module-info.class}} file > or an {{Automatic-Module-Name}} attribute in {{MANIFEST.MF}}), and > # the project using the dependency is itself modularized. > Condition #1 is fine, but #2 is problematic. The fact that a dependency is > declared on the class-path rather than the module-path changes the way that > {{java.util.ServiceLoader}} discovers the provided services. > * If the dependency is on the class-path, {{ServiceLoader}} scans the content > of {{META-INF/services}} directory. > * If the dependency is on the module-path, {{ServiceLoader}} uses the > declarations in {{module-info.class}}. > Even if condition #2 is false (i.e. a project is not modularized), > modularized dependencies still need to be declared on the module-path _for > allowing the dependency to discover its own services, or the services of a > transitive modularized dependency_. If a modularized dependency is put on the > class-path instead, it has consequence not only for the project using that > dependency, *but also for the dependency itself, which become unable to use > its own {{module-info.class}}*. > h1. Demonstration > The attached test case contains two Maven modules, named {{service}} and > {{client}}. The first Maven module declares a dummy services with 4 > providers, named A, B, C and D. Providers A and D are declared in > {{module-info}}. Providers B and C are declared in {{META-INF/services}}. A > {{ShowMyServices}} class lists the services discovered by > {{java.util.ServiceLoader}}. > The second Maven module has only a main method invoking {{ShowMyServices}}. > This second module intentionally has no {{module-info.java}} file. The use > case is a big module that we cannot modularize immediately (because > modularization brings stronger encapsulation, which requires significant > changes in the project to modularize), but still want to use modularized > dependencies. The test case can be run with {{mvn install}}. During test > execution, the following is printed: > {noformat} > Running test.client.MainTest > Start searching for services... > Provider B declared in META-INF. > Provider C declared in META-INF. > Done. > The dependency has been loaded as an unnamed module. > Consequently its `module-info` file has been ignored, > and the `META-INF/services` directory is used instead. > {noformat} > The above test demonstrates that {{module-info}} has been ignored in the > context of JUnit test execution. The same behaviour happens also with {{mvn > exec:java}} executed in the {{client}} sub-directory. > h2. Expected behaviour > The Maven behaviour can be reproduced on the command-line as below (Linux > convention). This command put everything on the class-path: > {code:bash} > java --class-path service/target/service-1.0.jar:client/target/client-1.0.jar > test.client.Main > {code} > The expected behaviour can be reproduced with the following command-line. > This command put the modularized dependency on the module-path while keeping > the non-modularized client on the class-path: > {code:bash} > java --module-path service/target/service-1.0.jar --class-path > client/target/client-1.0.jar --add-modules ALL-MODULE-PATH