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.

casual-greeting-service-provider

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.

formal-greeting-service-provider

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