In Java 9, we can develop Services and Service Providers as modules. A service module declares that it uses one or more interfaces whose implementations will be provided at run time by some provider modules. A provider module declares what implementations of service interfaces it provides.
We still have the option to deploying service providers on the class path (check out this example).
Deploying Services and Service Providers as modules
The Service Module
Let's say we have a service interface com.service.api.AnInterface. To declare this interface as service provider interface (SPI), we will use the "uses" clause in module-info.java:
module com.service.api {
exports com.service.api;
uses com.service.api.AnInterface;
}
The Service Provider Module
A service provider will use "provides .. with" clause to declare what service interface it intends to use (by using provides keyword) and what implementation of the interface it wants to expose (by using with keyword).
module com.service.provider {
requires msg.service.api;
provides com.service.api.AnInterface with com.service.provider.AnInterfaceImpl;
}
We don't have to specify the service implementation in a file under the resource directory META-INF/services. (Without modules, we still have to do that).
The Client Application using the Service
module com.example.app {
requires com.service.api;
}
Client application should be unaware of the provider implementation at compile time. We can deploy any providers during runtime via 'module-path'.
Discovering the Service implementations
We still need to use ServiceLoader#load(AnInterface.class) to discover service implementation instances. This is typically done in service API module itself.
Let's see a complete example to understand how to do that.
Example
In this example, we will create very simple service API module 'msg.service.api' which 'uses' a service interface to display a message. The example Provider will implement the interface and will show the message in a Java Swing dialog. We will also create a third project which will require the service interface and will have the provider deployed during runtime via module path.
The Service API
msg.service.api/src/msg/service/MsgService.javapackage msg.service;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
public interface MsgService {
static List<MsgService> getInstances() {
ServiceLoader<MsgService> services = ServiceLoader.load(MsgService.class);
List<MsgService> list = new ArrayList<>();
services.iterator().forEachRemaining(list::add);
return list;
}
void showMessage(String msg);
}
msg.service.api/src/module-info.javamodule msg.service.api {
exports msg.service;
uses msg.service.MsgService;
}
D:\java-modules\java-service-loader-example\msg.service.api>"tree /A /F" | \---src | module-info.java | \---msg \---service MsgService.java
Compiling the class:
D:\java-modules\java-service-loader-example\msg.service.api>javac -d out src/module-info.java src/msg/service/MsgService.java
Creating the jar:
D:\java-modules\java-service-loader-example\msg.service.api>jar --create --file msg-service.jar -C out .
Now we have following files:
D:\java-modules\java-service-loader-example\msg.service.api>"tree /A /F" | msg-service.jar | +---out | | module-info.class | | | \---msg | \---service | MsgService.class | \---src | module-info.java | \---msg \---service MsgService.java
The Service Provider
msg.service.provider.swing/src/msg/provider/swing/MsgServiceImpl.javapackage msg.provider.swing;
import msg.service.MsgService;
import javax.swing.*;
public class MsgServiceImpl implements MsgService{
@Override
public void showMessage(String msg) {
JOptionPane.showMessageDialog(null, msg);
}
}
msg.service.provider.swing/src/module-info.javamodule msg.service.provider.swing {
requires msg.service.api;
requires java.desktop;
provides msg.service.MsgService with msg.provider.swing.MsgServiceImpl;
}
Above module is also requiring 'java.desktop' which contains javax.swing.
Following is the directory structure of the provider project:
D:\java-modules\java-service-loader-example/msg.service.provider.swing>"tree /A /F" | \---src | module-info.java | \---msg \---provider \---swing MsgServiceImpl.java
Copying msg-lib.jar(the service API) to msg.service.provider.swing/lib, so that we can compile the provider classes:
D:\java-modules\java-service-loader-example\msg.service.provider.swing>mkdir lib
D:\java-modules\java-service-loader-example\msg.service.provider.swing>copy ..\msg.service.api\msg-service.jar lib\msg-service.jar 1 file(s) copied.
D:\java-modules\java-service-loader-example\msg.service.provider.swing>"tree /A /F" | +---lib | msg-service.jar | \---src | module-info.java | \---msg \---provider \---swing MsgServiceImpl.java
Compiling the classes:
D:\java-modules\java-service-loader-example\msg.service.provider.swing>javac -d out --module-path lib src/module-info.java src/msg/provider/swing/MsgServiceImpl.java
Creating the jar:
D:\java-modules\java-service-loader-example\msg.service.provider.swing>jar --create --file msg-service-swing.jar -C out .
D:\java-modules\java-service-loader-example\msg.service.provider.swing>"tree /A /F" | msg-service-swing.jar | +---lib | msg-service.jar | +---out | | module-info.class | | | \---msg | \---provider | \---swing | MsgServiceImpl.class | \---src | module-info.java | \---msg \---provider \---swing MsgServiceImpl.java
The Service Client Application
my-app/src/com/logicbig/AppMain.javapackage com.logicbig;
import msg.service.MsgService;
import java.util.List;
public class AppMain {
public static void main(String[] args) {
List<MsgService> msgServices = MsgService.getInstances();
for (MsgService msgService : msgServices) {
msgService.showMessage("A test message");
}
}
}
my-app/src/module-info.javamodule my.app {
requires msg.service.api;
}
D:\java-modules\java-service-loader-example/my-app>"tree /A /F" | \---src | module-info.java | \---com \---logicbig AppMain.java
Copying msg-lib.jar (Service API) to my-app/lib/msg-lib.jar
D:\java-modules\java-service-loader-example\my-app>mkdir lib
D:\java-modules\java-service-loader-example\my-app>copy ..\msg.service.api\msg-service.jar lib\msg-service.jar 1 file(s) copied.
Copying msg-service-swing.jar (the provider) to my-app/lib/msg-service-swing.jar:
D:\java-modules\java-service-loader-example\my-app>copy ..\msg.service.provider.swing\msg-service-swing.jar lib\msg-service-swing.jar 1 file(s) copied.
D:\java-modules\java-service-loader-example\my-app>"tree /A /F" | +---lib | msg-service-swing.jar | msg-service.jar | \---src | module-info.java | \---com \---logicbig AppMain.java
Compiling the classes:
D:\java-modules\java-service-loader-example\my-app>javac -d out --module-path lib src/module-info.java src/com/logicbig/AppMain.java
Running the application
D:\java-modules\java-service-loader-example\my-app>java --module-path out;lib --module my.app/com.logicbig.AppMain
Example ProjectDependencies and Technologies Used: |