Home > Software design >  Java9 modules : How to execute Provider based on some priority of execution?
Java9 modules : How to execute Provider based on some priority of execution?

Time:12-21

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

CodePudding user response:

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.

  • Related