The ServiceLoader.java docs notes:
It is strongly recommended that the application module does not require modules which contain providers of the service.
Why is this strongly recommended, what could happen if the recommendation isn't followed?
Context: This indirectly means that modules defining a service shouldn't also export a provider of that service. I thought it would be handy to provide a default implementation of the service within the same module.
The reason is because the uses
/ provivdes
directives and
the java.util.ServiceLoader
API is designed for a plugin-like architecture. You control which plugins are available by controlling which providers are on the module-path/class-path. If you requires
the provider module, then you can no longer omit it because the application would fail to launch due to missing dependencies.
The documentation you quote makes more sense if you look at the previous sentence as well, which provides more context.
In addition, if the application module does not contain the service, then its module declaration must have a requires directive that specifies the module which exports the service. It is strongly recommended that the application module does not require modules which contain providers of the service.
This is addressing the specific case demonstrated below.
Application Module
module app {
requires service;
}
Service Module
module service {
exports com.example.service;
uses com.example.service.Service;
}
Provider Module
module provider {
requires service;
provides com.example.service.Service with
com.example.provider.ServiceImpl;
}
The above is the recommended approach. And what the documentation is saying is that the app
module should not include a requires provider
directive. The reason why has already been explained.
Also, note this does not prevent either the service
module or the app
module from providing a default implementation of the service interface.
If you create and compile an implementation for the above modules, then you can see the module resolution at run-time via --show-module-resolution
. I use --limit-modules
below to control which modules are resolved to avoid having to mess around with the module-path. As you can see, since app
does not requires provider
, it is possible to omit provider
and still have a working application.
My service interface has a getMessage()
method that simply returns a String
. My main class iterates the available providers, if any, and outputs the provider's class name and "message". This output comes after, and is clearly distinct from, the module resolution output below.
Note I load the providers from a static method in the Service
interface, because the service
module is where I have the uses
directive for said interface.
With Provider Module
Command:
java --show-module-resolution --limit-modules app,service,provider --module-path <path> --module app/com.example.app.Main
Output:
root app <module-location>
app requires service <module-location>
service binds provider <module-location>
provider requires service <module-location>
APPLICATION OUTPUT
Provider: 'com.example.provider.ServiceImpl'
message => Hello, World!
Without Provider Module
Command:
java --show-module-resolution --limit-modules app,service --module-path <path> --module app/com.example.app.Main
Output:
root app <module-location>
app requires service <module-location>
APPLICATION OUTPUT
There are no available providers...
The previous edition of this answer had the following example to demonstrate what the documentation was saying:
Service Module
module service {
requires provider;
exports com.example.service;
uses com.example.service.Service;
}
Provider Module
module provider {
requires service;
provides com.example.service.Service with
com.example.provider.ServiceImpl;
}
Not only was I incorrect regarding the purpose of the documentation, but the above is not even possible as the Java Platform Module System does not allow cyclic dependencies at compile-time.