Issue
I am using java 9 modules to implement provider , i have multiple providers for service interface. I want to provide some priority for providers for their execution insted of using findFirst();
Optional<ServiceInterface> loader=ServiceLoader.load(ServiceInterface.class).findFirst();
I have service-interface
modules as below ,
ServiceInterface.Java
public interface ServiceInterface {
int theAnswer();
}
module-info.java
module ServiceInterface {
exports com.si.serviceinterface;
}
I have provider-module
which has two implementation for service interface ,
Provider1.java
public class Provider1 implements ServiceInterface {
@Override
public int theAnswer() {
return 42;
}
}
Provider2.java
public class Provider2 implements ServiceInterface {
@Override
public int theAnswer() {
return 52;
}
}
module-info.java
module Provider {
requires ServiceInterface;
provides ServiceInterface with Provider1, Provider2;
}
Now , i have consumer-module
which will use ServiceLoader
to load Provider. instead of using findFirst()
to load service provider. I want to load based on some priority example i want to load Provider2 then i should be able to load instead of loading provider 1.
Consumer.java
public class Consumer {
public static void main(String[] args) {
ServiceLoader<ServiceInterface> loader = ServiceLoader.load(ServiceInterface.class);
for (final ServiceInterface service : loader) {
System.out.println("The service " + service.getClass().getSimpleName() + " answers " + service.theAnswer());
}
}
}
any suggestions for implementing priority to load Providers instead of using findFirst()
.
Solution
The Designing services section of the ServiceLoader
documentation says
[…] 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.
Following this guideline, we can simply add a priority query method to the interface to match bullet one and keep the other stuff as-is, as the instantiation is not expensive.
public interface ServiceInterface {
int theAnswer();
int priority();
}
public class Provider1 implements ServiceInterface {
@Override
public int theAnswer() {
return 42;
}
@Override
public int priority() {
return 0;
}
}
public class Provider2 implements ServiceInterface {
@Override
public int theAnswer() {
return 52;
}
@Override
public int priority() {
return 1;
}
}
public class ServiceConsumer {
public static void main(String[] args) {
ServiceLoader<ServiceInterface> loader=ServiceLoader.load(ServiceInterface.class);
ServiceInterface service = loader.stream().map(Provider::get)
.max(Comparator.comparingInt(ServiceInterface::priority)).orElseThrow();
System.out.println("The service " + service.getClass().getSimpleName()
+ " answers " + service.theAnswer());
}
}
The service Provider2 answers 52
But since this is just an example, your use case might involve expensive-to-create service instances. In that case, you can follow the recommendation to separate the service provider interface and the actual service, as most JDK services do.
public interface ServiceProviderInterface {
/** Potentially expensive service instantiation */
ServiceInterface getService();
/** Can be cheaply queried without calling the expensive method */
int priority();
}
public interface ServiceInterface {
/**
* The operation
*/
int theAnswer();
/**
* Decide yourself if getting the provider is useful, e.g. java.nio.file.FileSystem
* has such a method, java.nio.charset.Charset has not.
*/
ServiceProviderInterface provider();
}
public class Provider1 implements ServiceProviderInterface {
public static class ActualService implements ServiceInterface {
private final ServiceProviderInterface provider;
public ActualService(Provider1 p) {
provider = p;
System.out.println("potentially expensive Provider1.ActualService()");
}
@Override
public int theAnswer() {
return 42;
}
@Override
public ServiceProviderInterface provider() {
return provider;
}
}
@Override
public ServiceInterface getService() {
return new ActualService(this);
}
@Override
public int priority() {
return 0;
}
}
public class Provider2 implements ServiceProviderInterface {
public static class ActualService implements ServiceInterface {
private final ServiceProviderInterface provider;
public ActualService(Provider2 p) {
provider = p;
System.out.println("potentially expensive Provider2.ActualService()");
}
@Override
public int theAnswer() {
return 52;
}
@Override
public ServiceProviderInterface provider() {
return provider;
}
}
@Override
public ServiceInterface getService() {
return new ActualService(this);
}
@Override
public int priority() {
return 1;
}
}
Of course, the module-info
declarations have to be adapted to provide or use ServiceProviderInterface
instead of ServiceInterface
. The use case will now look like
public class ServiceConsumer {
public static void main(String[] args) {
ServiceInterface service = ServiceLoader.load(ServiceProviderInterface.class)
.stream()
.map(Provider::get)
.max(Comparator.comparingInt(ServiceProviderInterface::priority))
.map(ServiceProviderInterface::getService)
.orElseThrow();
System.out.println("The service " + service.getClass().getSimpleName()
+ " answers " + service.theAnswer());
}
}
to the same outcome but not instantiating Provider1.ActualService()
. Only the actually used Provider2.ActualService()
is instantiated.
Alternatively to the guidelines of the documentation, you can use the first approach with an annotation instead of the priority()
method.
public interface ServiceInterface {
int theAnswer();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Priority {
int value();
}
@Priority(0)
public class Provider1 implements ServiceInterface {
public Provider1() {
System.out.println("potentially expensive Provider1()");
}
@Override
public int theAnswer() {
return 42;
}
}
@Priority(1)
public class Provider2 implements ServiceInterface {
public Provider2() {
System.out.println("potentially expensive Provider2()");
}
@Override
public int theAnswer() {
return 52;
}
}
public class ServiceConsumer {
public static void main(String[] args) {
ServiceInterface service = ServiceLoader.load(ServiceInterface.class).stream()
.max(Comparator.comparingInt(p->p.type().isAnnotationPresent(Priority.class)?
p.type().getAnnotation(Priority.class).value(): 0))
.map(Provider::get)
.orElseThrow();
System.out.println("The service " + service.getClass().getSimpleName()
+ " answers " + service.theAnswer());
}
}
This can avoid potentially expensive instantiations without the need to deal with two interfaces, however, the properties you can declare and query prior to the instantiation are limited to compile time constants.
On the other hand, this approach can be used to extend an already existing service framework, as the module providing the interface doesn’t need to know about the annotation(s). It’s possible to introduce them as an extended contract between certain service implementations and use sites.
Answered By - Holger