![]() | |
|
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:
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.
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.
![]() | |
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:
If the service provider declares a provider()
method, then the service loader invokes that method to obtain an
instance of the service provider. A provider method is a public static
method named "provider" with no formal
parameters and a return type that is assignable to the service's interface or class.
In this case, the service provider itself NEED NOT be assignable to the service's interface or class.
If the service provider does not declare a provider()
method, then the service provider is instantiated directly, via
its provider constructor. A provider constructor is a public constructor with no formal parameters.
In this case, the service provider MUST be assignable to the service's interface or class.
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.
![]() ![]() ![]() |