Why should an application module not require a module that provides a service?

388 Views Asked by At

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.

1

There are 1 best solutions below

3
On BEST ANSWER

Why Not Require Provider 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

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.

Example Module Resolution

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...

Previous Answer

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.