Service Loading in Java

The ServiceLoader class (see Chapter 6 of Volume I) provides a lightweight mechanism for matching up service interfaces with implementations. The Java Platform Module System makes this mechanism easier to use.

Here is a quick reminder of service loading. A service has an interface and one or more possible implementations. Here is a simple example of an interface:

public interface GreeterService

{

String greet(String subject);

Locale getLocale();

}

One or more modules provide implementations, such as

public class FrenchGreeter implements GreeterService

{

public String greet(String subject) { return “Bonjour ” + subject; }

public Locale getLocale() { return Locale.FRENCH; }

}

The service consumer must pick an implementation among all offered implementations, based on whatever criteria it deems appropriate.

ServiceLoader<GreeterService> greeterLoader = ServiceLoader.load(GreeterService.class);

GreeterService chosenGreeter;

for (GreeterService greeter : greeterLoader)

{

if (. . .)

{

chosenGreeter = greeter;

}

}

In the past, implementations were offered by placing text files into the META-INF/services directory of the JAR file containing the implementation classes. The module system provides a better approach. Instead of text files, you can add statements to the module descriptors.

A module providing an implementation of a service adds a provides statement that lists the service interface (which may be defined in any module) and the implementing class (which must be a part of this module). Here is an example from the jdk.security.auth module:

module jdk.security.auth

{

provides javax.security.auth.spi.LoginModule

with com.sun.security.auth.module.Krb5LoginModule,

com.sun.security.auth.module.UnixLoginModule,

com.sun.security.auth.module.JndiLoginModule,

com.sun.security.auth.module.KeyStoreLoginModule,

com.sun.security.auth.module.LdapLoginModule,

com.sun.security.auth.module.NTLoginModule;

}

This is the equivalent of the META-INF/services file.

A consuming module contains a uses statement.

module java.base

{

uses javax.security.auth.spi.LoginModule;

}

When code in a consuming module calls ServiceLoader.load(Servicelnterface.class), the matching provider classes will be loaded, even though they may not be in accessible packages.

In our code example, we provide implementations for a German and French greeter in the package com.horstmann.greetsvc.internal. The service module exports the com.horstmann.greetsvc package, but not the package with the implementations. The provides statement declares the service and its implementing classes in the unexported package:

module com.horstmann.greetsvc

{

exports com.horstmann.greetsvc;

provides com.horstmann.greetsvc.GreeterService with

com.horstmann.greetsvc.internal.FrenchGreeter,

com.horstmann.greetsvc.internal.GermanGreeterFactory;

}

The v2ch09.useservice module consumes the service. Using the ServiceLoader facility, we iterate over the provided services and pick the one matching the desired language:

package com.horstmann.hello;

import java.util.*;

import com.horstmann.greetsvc.*;

public class HelloWorld

{

public static void main(String[] args)

{

ServiceLoader<GreeterService> greeterLoader

= ServiceLoader.load(GreeterService.class);

String desiredLanguage = args.length > 0 ? args[0] : “de”;

GreeterService chosenGreeter = null;

for (GreeterService greeter : greeterLoader)

{

if (greeter.getLocale().getLanguage().equals(desiredLanguage))

chosenGreeter = greeter;

}

if (chosenGreeter == null)

System.out.println(“No suitable greeter.”);

else

System.out.println(chosenGreeter.greet(“Modular World”));

}

}

The module declaration requires the service module and declares that the GreeterService is being used.

module v2ch09.useservice

{

requires com.horstmann.greetsvc;

uses com.horstmann.greetsvc.GreeterService;

}

As a result of the provides and uses declarations, the module that consumes the service is allowed access to the private implementation classes.

To build and run the program, first compile the service:

javac com.horstmann.greetsvc/module-info.java \

com.horstmann.greetsvc/com/horstmann/greetsvc/GreeterService.java \

com.horstmann.greetsvc/com/horstmann/greetsvc/internal/*.java

Then compile and run the consuming module:

javac -p com.horstmann.greetsvc \

v2ch09.useservice/com/horstmann/hello/HelloWorld.java \

v2ch09.useservice/module-info.java

java -p com.horstmann.greetsvc:v2ch09.useservice \

-m v2ch09.useservice/com.horstmann.hello.HelloWorld

Source: Horstmann Cay S. (2019), Core Java. Volume II – Advanced Features, Pearson; 11th edition.

Leave a Reply

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