Instead of building monolithic applications developers prefer modular and extendible applications. Either they want to use the flexible architecture by themselfs or provide a plug-in api for other developers. In both situations one must define a modul’s purpose and it’s boundary. This module boundary is also called the service provides interface (SPI) or also a plug-in api. Java’s jar file specification describes how service providers can provide their services so that others can lookup them. This pattern is known as the service locator pattern. Since Java 1.6 the ServiceLoader has been added in order to make service provider lookup easy for application developers. Prior to Java 1.6 applications developers had to implement the lookup on their own based on the jar file specification. But you can also use third-party libraries like commons-discovery.
In this blog I want to demonstrate how you can use standard java to locate service providers.
The service provider interface (SPI)
First we must define an interface for a service that can be plugged in. In this example I will just use a simple greeting service, because the focus of this blog is on using a spi.
public interface GreetingService { public String greet(Person person); } public interface Person { public String getFirstname(); public String getLastname(); public Gender getGender(); } public enum Gender { MALE, FEMALE, UNKNOWN; }
Service provider implementations
Service providers must implement the service provider interface and should be bundled in seperate jar files. Even if multiple service providers can be bundled in one jar it is better to divide them in seperate jars so that you can simply add or remove them from the classpath in order to activate or deactivate a service provider.
A casual greeting service provider
The next step is to implement the service provider interface to provide a specific service. The next source code example shows a simple casual greeting service.
public class CasualGreetingService implements GreetingService { public String greet(Person person) { String firstname = person.getFirstname(); String greeting = MessageFormat.format("Hello {0}", firstname); return greeting; } }
After the service provider has been implemented you publish that service as described by the jar file specification.
To do that you must define a text file that contains the full-quallified provider class name. This text file is named the service provider configuration and must be named after the full-quallified interface name and placed in META-INF/services.
Another formal greeting service provider
In this blog I also want to demonstrate how service providers can be activated and deactivated using the classpath. Therefore I add another service provider here.
public class FormalGreetingService implements GreetingService { private Map<Gender, String> titleMap = new HashMap<Gender, String>(); public FormalGreetingService() { titleMap.put(Gender.MALE, "Mr."); titleMap.put(Gender.FEMALE, "Ms."); } public String greet(Person person) { StringBuilder greetingBuilder = new StringBuilder(); greetingBuilder.append("Dear "); Gender gender = person.getGender(); String title = titleMap.get(gender); if (title != null) { greetingBuilder.append(title); greetingBuilder.append(" "); } String lastname = person.getLastname(); greetingBuilder.append(lastname); String greeting = greetingBuilder.toString(); return greeting; } }
Of course this service provider also needs to be published using a service provider configuration file.
Lookup and use service providers
Since Java 1.6. you can use the ServiceLoader api to lookup service providers for an service interface. In this simple example I will use a small main class that does the job.
public class GreetingApp { public static void main(String[] args) { ServiceLoader<GreetingService> greetingServiceLoader = ServiceLoader .load(GreetingService.class); Iterator<GreetingService> greetingServices = greetingServiceLoader .iterator(); if (!greetingServices.hasNext()) { throw new IllegalStateException("No GreetingService provider found"); } Person person = new MainArgsPersonAdapter(args); while (greetingServices.hasNext()) { GreetingService greetingService = greetingServices.next(); String greeting = greetingService.greet(person); Class<? extends GreetingService> serviceImplClass = greetingService .getClass(); String simpleServiceName = serviceImplClass.getSimpleName(); String msg = MessageFormat.format("Greeting by provider {0}: {1}", simpleServiceName, greeting); System.out.println(msg); } } private static class MainArgsPersonAdapter implements Person { private String[] args; public MainArgsPersonAdapter(String[] args) { this.args = args; if (args.length < 2) { throw new IllegalArgumentException( "You must specify at least a firstname and lastname"); } } public String getFirstname() { return args[0]; } public String getLastname() { return args[1]; } public Gender getGender() { Gender sex = Gender.UNKNOWN; if (args.length > 2) { String sexArg = args[2].toUpperCase(); sex = Gender.valueOf(sexArg); } return sex; } } }
Build the example code
You can download the compiled example code here as zip file. (Java 1.7 needed)
or build it. You can get the full source code at github.
Run the example with different classpath
In the example code zip file you will find these jars
- java-spi.jar
This jar only contains the service provider interface. - java-spi-client.jar
This jar file contains the main class that looks up service providers - java-spi-casual-greeting-service.jar
This jar only contains the casual greeting service implementation. - java-spi-formal-greeting-service.jar
This jar only contains the formal greeting service implementation.
In the example zip file you will also find two bat files that you can use on windows.
If you run the example with only the casual greeting service the classpath will look like this.
java -cp java-spi.jar;java-spi-casual-greeting-service.jar;java-spi-client.jar com.link_intersystems.blog.plugin.client.GreetingApp René Link MALE Greeting by provider CasualGreetingService: Hello René
or replace the casual greeting service with the formal greeting service
java -cp java-spi.jar;java-spi-formal-greeting-service.jar;java-spi-client.jar com.link_intersystems.blog.plugin.client.GreetingApp René Link MALE Greeting by provider FormalGreetingService: Dear Mr. Link
At least you can also run the example with both services
java -cp java-spi.jar;java-spi-casual-greeting-service.jar;java-spi-formal-greeting-service.jar;java-spi-client.jar com.link_intersystems.blog.plugin.client.GreetingApp René Link MALE Greeting by provider CasualGreetingService: Hello René Greeting by provider FormalGreetingService: Dear Mr. Link