Java Modularity Part 4 – Services

Java modularity

Last week we looked at the exports and requires directives in more detail. This week we’ll cover services contained in modules. These are specified with the uses and provides directives.

If you missed the previous posts in this sequence, you can find them here:

Services

A service is a class or application that provides some functionality to an application that calls it.

We can have different service implementations available, and allow our application to choose the most appropriate one at runtime.

We create the service by defining the interface. We then create the classes that implement that interface. These classes are called service providers. The application can use a loader to locate and load service providers as needed.

Our application code refers to the service only by its interface, not by the specific service providers. The service providers are deployed in the runtime environment. Our application chooses the most appropriate service provider it needs at that time.

When we define the service interface, we should conform to the Interface Segregation Principle (ISP) which says that an interface should be as small and cohesive as possible. We create narrow interfaces which are specific to one purpose.

Here’s a simple example of a service interface:

package com.incusdata.helloservice;

public interface HelloService {
   public String hello(String name);
}

We then create one or more implementation classes, such as:

public class EnglishHelloServiceImpl implements HelloService {
   public String hello(String name) { 
      return "Hello " + name;
   }
}

public class ZuluHelloServiceImpl implements HelloService {
   public String hello(String name) { 
      return "Sawubona " + name;
   }
}

ServiceLoaders

Since Java 1.6, we can easily load different service provider implementations by using the java.util.ServiceLoader class.

Here is a portion of application code that selects the most appropriate service provider from those available:

Iterable services = ServiceLoader.load(HelloService.class);
HelloService helloService;
for (HelloService hs : services) {
    if (...) {  // some logic to choose the most appropriate provider
        helloService = hs;
        // use the selected service object here
        ...
    }
}

Services as part of Java Modularity

Up to Java 9, we specified the service providers in text files in the META-INF/services directory of the JAR file containing the implementation classes.

From Java 9, we can use the module system to do this. Instead of text files, we use module descriptors.

We will have the following three components:

  • The service interface is usually in a service interface module.
  • The service implementations are provided by separate modules. These implementations are not in the service interface module. Usually each service implementation module will contain a single service implementation.
  • The service client will use the service interface module to code against the service interface. It doesn’t know exactly which module will implement the service. The service providers are discovered at runtime depending on which service implementation modules are available on the Java module path.

Service Interface Module

The service interface module is just a normal Java module that exports the package containing the service interface.

Let’s place the previous HelloService interface in a module. Here the com.incusdata.helloservice module exports the com.incusdata.helloservice package:

module com.incusdata.helloservice {
   exports com.incusdata.helloservice;
}

Service Implementation Module

To create a Java module that implements a service interface, we must:

  • Specify that the service interface module is required.
  • Implement the service interface with a concrete Java class.
  • Specify the service implementation class in the module descriptor.

Let’s create a service implementation module for the HelloService interface. We hadn’t shown a package in the previous implementation code. Let’s assume the service provider class is in the com.acme.myservice package. Our module descriptor would contain the following:

module com.acme.myservice {
   requires com.incusdata.helloservice;

   provides com.incusdata.helloservice.HelloService with
      com.acme.myservice.EnglishHelloServiceImpl;
}

The module descriptor firstly requires the service interface module. Then it provides an implementation for the com.incusdata.helloservice.HelloService interface with the class com.acme.myservice.EnglishHelloServiceImpl.

Service Client Module

Once we have a service interface module and one or more service implementation modules, we can create a service client module that uses the service.

To use the service, the client module must specify that it uses the service, as follows:

module com.incusdata.client {
   requires com.incusdata.helloservice;
   uses     com.incusdata.helloservice.HelloService;
}

The client module descriptor also requires the com.incusdata.helloservice module which contains the service interface. Only the service interface module is required. The service implementation modules are looked up at runtime.

We decide what service implementation to use when loading/running the application. We do this by putting the service implementation module(s) into the module path. The service interface and client modules are then decoupled from the service implementation modules.

The service client module can look up a service provider implementation at runtime with the same code as before:

Iterable services = ServiceLoader.load(HelloService.class);
...

The returned Iterator contains a list of all HelloService implementations found in the modules found on the module path. The application can then iterate through the service implementations to find the one it wants to use.

What’s Next?

In the next post, we’ll look at how reflection works with modules.

Did this help? Please share your thoughts and comments.

Leave a Comment

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

Code like a Java Guru!

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.

Your Java tip is on its way!

Check that incusdata.com is an approved sender, so that your Java tips don’t land up in the spam folder.

Our privacy policy means your data is safe. You can unsubscribe from these tips at any time.