Implementing the Service Locator – Java Module System

Implementing the Service Locator

The class java.util.ServiceLoader<S>, where the type parameter S designates the type of service, is at the core of finding and loading service providers for the service S. A service loader for a specific service is created by calling the static method load() of the ServiceLoader class, passing the runtime Class object of the service interface, as shown below. Typically, a stream is then created by calling the stream() method on the service loader. This stream can be used to lazily load the available service providers for the specified service S. The type of an element in this stream is the static member interface ServiceProvider.Provider<S>.

Click here to view code image

ServiceLoader<IAdvice> loader = ServiceLoader.load(IAdvice.class);
Stream<ServiceLoader.Provider<IAdvice>> spStream = loader.stream();

The procedure above allows processing of stream elements as type Provider<S> without instantiating the service provider. The get() method of the Provider<S> interface must be invoked on a stream element of type Provider<S> to obtain an instance of the service provider—that is, the class that implements the service interface S. Given the service provider, the service implemented by the provider can be utilized.

Click here to view code image

static <S> ServiceLoader<S> load(Class<S> service)

This static method creates a service loader for the given service type specified by its runtime object, using the current thread’s context class loader.

Click here to view code image

Stream<ServiceLoader.Provider<S>> stream()

Returns a stream that lazily loads available providers of this loader’s service (designated by type parameter S). The stream elements are of type Service-Loader.Provider<S>.

The interface Provider<S> is a static member interface of the class Service-Loader<S> that extends the java.util.function.Supplier<S> functional interface. The get() method of the Provider<S> interface must be invoked to get or instantiate the service provider for the service S.

A service locator for the advice service is implemented by the class org.advice .servicelocator.AdviceLocator in the servicelocator module (Example 19.5). The class provides two static methods, getAdvice() and getAllAdvice(), which return a service provider that implements the IAdvice service interface for a particular locale or all service providers that implement the IAdvice service interface, as shown at (1) and (8), respectively. In other words, the former returns a service provider for a particular locale and the latter returns all service providers that implement the IAdvice service interface.

The method getAdvice() at (1) is passed the desired locale. Its return type is Optional<IAdvice>, as there might not be any service provider for this service for the desired locale. At (2), a service loader is created for the IAdvice service interface. A stream is built with the service loader as the source at (3). This stream of element type Provider<IAdvice> is processed to find a service provider for the desired locale. Note how at (5), the stream is converted from type Stream<ServiceLoader.Provider<IAdvice>> to Stream<IAdvice> by the map() intermediate operation that applies the get() method of the Provider<S> interface to each element. The resulting stream is filtered at (6) for a service provider with the desired locale. The first service provider that has the desired locale is selected by the findFirst() terminal operation at (7). This operation returns an Optional<IAdvice> as there might not be such a service provider in the stream.

Analogously, the method getAllAdvice() at (8) collects all service providers for the IAdvice service interface in a list and returns them. In this case, an empty list will indicate that no service providers were found.

Example 19.5 Implementing the Advice Service Locator

Click here to view code image

// File:./src/servicelocator/org/advice/servicelocator/AdviceLocator.java
package org.advice.servicelocator;
import java.util.*;
import java.util.stream.*;
import org.advice.si.*;
public class AdviceLocator {
  /** Get advice for a particular locale. */
  public static Optional<IAdvice> getAdvice(Locale desiredLocale) {     // (1)
    ServiceLoader<IAdvice> loader = ServiceLoader.load(IAdvice.class);  // (2)
    Stream<ServiceLoader.Provider<IAdvice>> spStream = loader.stream(); // (3)
    Optional<IAdvice> optAdvice = spStream                              // (4)
      .map(ServiceLoader.Provider::get)                   // (5) Stream<IAdvice>
      .filter(a -> desiredLocale.equals(a.getLocale()))   // (6)
      .findFirst();                                       // (7)
    return optAdvice;
  }
  /** Get all advice implemented by all providers. */
  public static List<IAdvice> getAllAdvice() {            // (8)
    ServiceLoader<IAdvice> loader = ServiceLoader.load(IAdvice.class);
    List<IAdvice> allAdvice = loader
      .stream()
      .map(p -> p.get())                  // (9) map(ServiceLoader.Provider::get)
      .collect(Collectors.toList());      // (10) List<IAdvice>
    return allAdvice;
  }
}

At (1) below, the servicelocator module (Figure 19.15(b)) requires the serviceinterface module in order to locate its service providers, and it exports the package org.advice.servicelocator at (2) so that service consumers can locate service providers. The uses directive at (3) declares which service (org.advice.si.IAdvice) is used by this module, allowing it to look up this particular service at runtime.

Click here to view code image

// File: ./src/servicelocator/module-info.java
module servicelocator {
  requires serviceinterface;           // (1)
  exports org.advice.servicelocator;   // (2)
  uses org.advice.si.IAdvice;          // (3) In module serviceinterface.
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *