2.2.  Design a service type, load the services using ServiceLoader, check for dependencies of the services including consumer module and provider module

[Note]

Designing services

A service is a single type, usually an interface or abstract class. A concrete class can be used, but this is not recommended. The type may have any accessibility. The methods of a service are highly domain-specific, so this API specification cannot give concrete advice about their form or function. However, there are two general guidelines:

  1. A service should declare as many methods as needed to allow service providers to communicate their domain-specific properties and other quality-of-implementation factors. An application which obtains a service loader for the service may then invoke these methods on each instance of a service provider, in order to choose the best provider for the application.

  2. A service should express whether its service providers are intended to be direct implementations of the service or to be an indirection mechanism such as a "proxy" or a "factory". Service providers tend to be indirection mechanisms when domain-specific objects are relatively expensive to instantiate; in this case, the service should be designed so that service providers are abstractions which create the "real" implementation on demand. For example, the CodecFactory service expresses through its name that its service providers are factories for codecs, rather than codecs themselves, because it may be expensive or complicated to produce certain codecs.

Developing service providers

A service provider is a single type, usually a concrete class. An interface or abstract class is permitted because it may declare a static provider() method, discussed later. The type must be public and must not be an inner class.

A service provider and its supporting code may be developed in a module, which is then deployed on the application module path or in a modular image. Alternatively, a service provider and its supporting code may be packaged as a JAR file and deployed on the application class path. The advantage of developing a service provider in a module is that the provider can be fully encapsulated to hide all details of its implementation.

An application that obtains a service loader for a given service is indifferent to whether providers of the service are deployed in modules or packaged as JAR files. The application instantiates service providers via the service loader's iterator, or via Provider objects in the service loader's stream, without knowledge of the service providers' locations.

We can update app.Client class (from previous section) as follows:

package app;

import java.util.ServiceLoader;
import java.util.ServiceLoader.Provider;
import p1.GreeterIntf;

public class Client {
    public static void main(String[] args) {
        ServiceLoader.load(GreeterIntf.class)
                .stream()                
                .filter((Provider p) -> p.type().getSimpleName().startsWith("Greeter"))
                .map(Provider::get)
                .findFirst()
                .ifPresent(s -> s.greet());
    }
}
					

An instance of the ServiceLoader.Provider interface represents a service provider. Its type() method returns the Class object of the service implementation. The get() method instantiates and returns the service provider. When you use the stream() method, each element in the stream is of the ServiceLoader.Provider type. You can filter the stream based on the class name or type of the provider, which will not instantiate the provider. You can use the type() method in your filters. When you find the desired provider, call the get() method to instantiate the provider. This way, you instantiate a provider when you know you need it, not when you are iterating through all providers.

Deploying service providers as modules

A service provider that is developed in a module must be specified in a provides directive in the module declaration. The provides directive specifies both the service and the service provider; this helps to locate the provider when another module, with a uses directive for the service, obtains a service loader for the service. It is strongly recommended that the module does not export the package containing the service provider. There is no support for a module specifying, in a provides directive, a service provider in another module.

[Important]

This a bad example of module definition:

module modP {
    requires modS;
    provides p1.GreeterIntf with p2.GreeterImpl;
    exports p2; // BAD !!! We should not export implementation of the service 
}
						

A service provider that is developed in a module has no control over when it is instantiated, since that occurs at the behest of the application, but it does have control over how it is instantiated:

A service provider that is deployed as an automatic module on the application module path must have a provider constructor. There is no support for a provider() method in this case.

Let's create new service provider:

package p3;

import p1.GreeterIntf;

public class MyProvider {
    public static GreeterIntf provider() {
        return new GreeterIntf() {
            @Override
            public void greet() {
                System.out.println("Greeting from MyProvider !");
            }
        };
    }
}

					

module modPP {
    requires modS;
    provides p1.GreeterIntf with p3.MyProvider;
}
					

if you update the client class filter as follows:

...					
.filter((Provider p) -> p.type().getSimpleName().startsWith("GreeterIntf"))
...
					

The output will be:

Greeting from MyProvider !
					

Two points here: (a) the provider() method was used to instantiate service implementation, and (b) the service provider type (MyProvider) is not assignable to service interface (GreeterIntf).

Service module dependency

Run the command:

C:\1Z0-817>jar --describe-module --file=service.jar

modS jar:file:///C:/1Z0-817/service.jar/!module-info.class
exports p1
requires java.base mandated					
					

As you can see is depends on java.base module in our case (it always implicitly added).

Provider module dependency

Run the command:

C:\1Z0-817>jar --describe-module --file=provider.jar

modP jar:file:///C:/1Z0-817/provider.jar/!module-info.class
requires java.base mandated
requires modS
provides p1.GreeterIntf with p2.GreeterImpl
contains p2
					

As you can see is depends on java.base and modS service module.

Client module dependency

Run the command:

C:\1Z0-817>jar --create --file service-client.jar -C service-client .

C:\1Z0-817>jar --describe-module --file=service-client.jar

modC jar:file:///C:/1Z0-817/service-client.jar/!module-info.class
requires java.base mandated
requires modS
uses p1.GreeterIntf
contains app
					

As you can see is depends on java.base and modS service module. The client module (modC) does not depend on provider module (modP) and not aware of it at compile time.

Professional hosting         Exam 1Z0-817: Upgrade OCP Java 6, 7 & 8 to Java SE 11 Developer Quiz     Exam 1Z0-810: Upgrade to Java SE 8 Programmer Quiz