Unnamed module interaction with named module by ServiceLoader::load

Andrew Sasha picture Andrew Sasha · Jan 18, 2018 · Viewed 7.2k times · Source

I have a project like this:

\---main
    \---src
        \---com.foo
            \---UnnamedStart.java
\---api
    \---src
        \---com.foo.api
            \---ApiInterface.java
        \---module-info.java
\---impl
    \---src
        \---com.foo.impl
            \---ApiInterfaceImpl.java
        \---module-info.java

Implementatio of UnnamedStart.java is:

public static void main(String[] args) {
    ServiceLoader<ApiInterface> services = ServiceLoader.load(ApiInterface.class);
    ...
}

Note that main is unnamed module.

api/src/module-info.java is:

module com.foo.api {
     exports com.foo.api;
}

and impl/src/module-info.java is:

update 1.1 - code below updated see comments, added requires

update 1.2 - code below updated, provides A with B changed to provides B with A mistake during creating question, originally was ok

module com.foo.impl {
     requires com.foo.api; //added (update 1.1)
     provides com.foo.impl.ApiInterface
         with com.foo.api.ApiInterfaceImpl; //vice versa (update 1.2)
}

When I run my code in UnnamedStart.java I end up with no element in services.

I also tried to create a static method in com.foo.api.ApiInterface:

static List<ApiInterface> getInstances() {
    ServiceLoader<ApiInterface> services = ServiceLoader.load(ApiInterface.class);
    List<ApiInterface> list = new ArrayList<>();
    services.iterator().forEachRemaining(list::add);
    return list;
}

and add in api/src/module-info.java line uses com.foo.api.ApiInterface; but it gave the same result (nothing).

The only way I made it work is by migrating main from unnamed to named module.

1. How does java 9 work when unnamed module trying to interact with named module?

2. Does it possible to make it work and keeping the main like unnamed module?

update 1.3 - added related project

Answer

Andrew Sasha picture Andrew Sasha · Jan 21, 2018

ServiceLoader::load works as usual, but the are other things.

[Short answer]

1. Unnamed module reads the same like named module to named module, but named module can not access types in the unnamed module.

2. You are trying to launch an application from a non-modular JAR so you have to explicitly resolve required modules by --add-modules com.foo.impl.

Note that your required modules have to be on module graph (e.g. add by --module-path).

[More details]

1. There are 4 different types of modules: built-in platform module, named module, automatic module, unnamed module and each of them are named apart from unnamed module

As they wrote the unnamed module treats all the other modules the same like named module:

All other modules have names, of course, so we will henceforth refer to those as named modules.

The unnamed module reads every other module. [...]

The unnamed module exports all of its packages. [...] It does not, however, mean that code in a named module can access types in the unnamed module. A named module cannot, in fact, even declare a dependence upon the unnamed module. [...]

If a package is defined in both a named module and the unnamed module then the package in the unnamed module is ignored.

Even an automatic module indeed is also named:

An automatic module is a named module that is defined implicitly, since it does not have a module declaration.

2. Second part of this answer

If you compile non-modular code or launch an application from a non-modular JAR, the module system is still in play and because non-modular code does not express any dependencies, it will not resolve modules from the module path.

So if non-modular code depends on artifacts on the module path, you need to add them manually with the --add-modules option. Not necessarily all of them, just those that you directly depend on (the module system will pull in transitive dependencies) - or you can use ALL-MODULE-PATH (check the linked post, it explains this in more detail).

This @nullpointer comment will be useful

Also, the module resolution still needed the impl to be resolved during the startup. To check which you could also make use of the --show-module-resolution flag.