Creating Extensible Applications

The following topics are covered:

Introduction

An extensible application is one that you can extend without modifying its original code base. You can enhance its functionality with new plug-ins or modules. Developers, software vendors, and customers can add new functionality or application programming interfaces (APIs) by adding a new Java Archive (JAR) file onto the application class path or into an application-specific extension directory.

This section describes how to create applications with extensible services, which enable you or others to provide service implementations that require no modifications to the original application. By designing an extensible application, you provide a way to upgrade or enhance specific parts of a product without changing the core application.

One example of an extensible application is a word processor that allows the end user to add a new dictionary or spelling checker. In this example, the word processor provides a dictionary or spelling feature that other developers, or even customers, can extend by providing their own implementation of the feature.

The following are terms and definitions important to understand extensible applications:

  • Service

    • A set of programming interfaces and classes that provide access to some specific application functionality or feature. The service can define the interfaces for the functionality and a way to retrieve an implementation. In the word-processor example, a dictionary service can define a way to retrieve a dictionary and the definition of a word, but it does not implement the underlying feature set. Instead, it relies on a service provider to implement that functionality.
  • Service provider interface (SPI)

    • The set of public interfaces and abstract classes that a service defines. The SPI defines the classes and methods available to your application.
  • Service Provider

    • Implements the SPI. An application with extensible services enable you, vendors, and customers to add service providers without modifying the original application.

Dictionary Service Example

Consider how you might design a dictionary service in a word processor or editor. One way is to define a service represented by a class named DictionaryService and a service provider interface named Dictionary . The DictionaryService provides a singleton DictionaryService object. (See the section The Singleton Design Pattern for more information.) This object retrieves definitions of words from Dictionary providers. Dictionary service clients — your application code — retrieve an instance of this service, and the service will search, instantiate, and use Dictionary service providers.

Although the word-processor developer would most likely provide a basic, general dictionary with the original product, the customer might require a specialized dictionary, perhaps containing legal or technical terms. Ideally, the customer is able to create or purchase new dictionaries and add them to the existing application.

The DictionaryServiceDemo sample shows you how to implement a Dictionary service, create Dictionary service providers that add additional dictionaries, and create a simple Dictionary service client that tests the service. This sample, which is packaged in the zip file DictionaryServiceDemo.zip , consists of the following files:

Note : The build directories contain the compiled class files of the Java source files contained in the src directory in the same level.

Running the DictionaryServiceDemo Sample

Because the zip file DictionaryServiceDemo.zip contains compiled class files, you can unzip this file to your computer and run the sample without compiling it by following these steps:

  • Download and unzip the sample code: Download and unzip the file DictionaryServiceDemo.zip to your computer. It is These steps assume that you unzipped the contents of this file into the directory C:\DictionaryServiceDemo .

  • Change the current directory to C:\DictionaryServiceDemo\DictionaryDemo and follow the step Run the Client.

Compiling and Running the DictionaryServiceDemo Sample

The DictionaryServiceDemo sample includes Apache Ant build files, which are all named build.xml . The following steps show you how to use Apache Ant to compile, build, and run the DictionaryServiceDemo sample:

  • Install Apache Ant: Go to the following link to download and install Apache Ant:

http://ant.apache.org/

Ensure that the directory that contains the Apache Ant executable file is in your PATH environment variable so that you can run it from any directory. In addition, ensure that your JDK's bin directory, which contains the java and javac executables ( java.exe and javac.exe for Microsoft Windows). is in your PATH environment variable. See PATH and CLASSPATH for information about setting the PATH environment variable.

  • Download and unzip the sample code: Download and unzip the file DictionaryServiceDemo.zip to your computer. These steps assume that you unzipped the contents of this file into the directory C:\DictionaryServiceDemo .

  • Compile the code: Change the current directory to C:\DictionaryServiceDemo and run the following command:

ant compile-all

This command compiles the source code in the src directories contained in the directories DictionaryDemo , DictionaryServiceProvider , ExtendedDictionary , and GeneralDictionary , and puts the generated class files in the corresponding build directories.

  • Package the compiled Java files into JAR files: Ensure the current directory is C:\DictionaryServiceDemo and run the following command:
ant jar

This command creates the following JAR files:

  • DictionaryDemo/dist/DictionaryDemo.jar

  • DictionaryServiceProvider/dist/DictionaryServiceProvider.jar

  • GeneralDictionary/dist/GeneralDictionary.jar

  • ExtendedDictionary/dist/ExtendedDictionary.jar

  • Run the sample: Ensure that the directory that contains the java executable is in your PATH environment variable. See PATH and CLASSPATH for more information.

Change the current directory to C:\DictionaryServiceDemo\DictionaryDemo and run the following command:

ant run

The sample prints the following:

book: a set of written or printed pages, usually bound with a protective cover editor: a person who edits xml: a document standard often used in web services, among other things REST: an architecture style for creating, reading, updating, and deleting data that attempts to use the common vocabulary of the HTTP protocol; Representational State Transfer

Understanding the DictionaryServiceDemo Sample

The following steps show you how to re-create the contents of the file DictionaryServiceDemo.zip . These steps show you how the sample works and how to run it.

1. Define the Service Provider Interface

The DictionaryServiceDemo sample defines one SPI, the Dictionary.java interface. It contains only one method:

package dictionary.spi;

public interface Dictionary {
    public String getDefinition(String word);
}

The sample stores the compiled class file in the directory DictionaryServiceProvider/build .

2. Define the Service That Retrieves the Service Provider Implementations

The DictionaryService.java class loads and accesses available Dictionary service providers on behalf of dictionary service clients:

package dictionary;

import dictionary.spi.Dictionary;
import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;

public class DictionaryService {

    private static DictionaryService service;
    private ServiceLoader<Dictionary> loader;

    private DictionaryService() {
        loader = ServiceLoader.load(Dictionary.class);
    }

    public static synchronized DictionaryService getInstance() {
        if (service == null) {
            service = new DictionaryService();
        }
        return service;
    }

    public String getDefinition(String word) {
        String definition = null;

        try {
            Iterator<Dictionary> dictionaries = loader.iterator();
            while (definition == null && dictionaries.hasNext()) {
                Dictionary d = dictionaries.next();
                definition = d.getDefinition(word);
            }
        } catch (ServiceConfigurationError serviceError) {
            definition = null;
            serviceError.printStackTrace();

        }
        return definition;
    }
}

The sample stores the compiled class file in the directory DictionaryServiceProvider/build .

The DictionaryService class implements the singleton design pattern. This means that only a single instance of the DictionaryService class is ever created. See the section The Singleton Design Pattern for more information.

The DictionaryService class is the dictionary service client's entry point to using any installed Dictionary service provider. Use the ServiceLoader.load method to retrieve the private static member DictionaryService.service , the singleton service entry point. Then the application can call the getDefinition method, which iterates through available Dictionary providers until it finds the targeted word. The getDefinition method returns null if no Dictionary instance contains the specified definition of the word.

The dictionary service uses the ServiceLoader.load method to find the target class. The SPI is defined by the interface dictionary.spi.Dictionary , so the example uses this class as the load method's argument. The default load method searches the application class path with the default class loader.

However, an overloaded version of this method enables you to specify custom class loaders if you wish. That enables you to do more sophisticated class searches. A particularly enthusiastic programmer might, for example, create a ClassLoader instance that can search in an application-specific subdirectory that contains provider JARs added during runtime. The result is an application that does not require a restart to access new provider classes.

After a loader for this class exists, you can use its iterator method to access and use each provider that it finds. The getDefinition method uses a Dictionary iterator to go through the providers until it finds a definition for the specified word. The iterator method caches Dictionary instances, so successive calls require little additional processing time. If new providers have been placed into service since the last invocation, the iterator method adds them to the list.

The DictionaryDemo.java class uses this service. To use the service, the application obtains a DictionaryService instance and calls the getDefinition method. If a definition is available, the application prints it. If a definition is not available, the application prints a message stating that no available dictionary carries the word.

The Singleton Design Pattern

A design pattern is a general solution to a common problem in software design. The idea is that the solution gets translated into code, and that code can be applied in different situations where the problem occurs. The singleton pattern describes a technique to ensure that only a single instance of a class is ever created. In essence, the technique takes the following approach: Do not let anyone outside the class create instances of the object.

For example, the DictionaryService class implements the singleton pattern as follows:

  • Declares the DictionaryService constructor as private , which prevents all other classes, except DictionaryService , from creating instances of it.

  • Defines the DictionaryService member variable service as static , which ensures only one instance of DictionaryService exists.

  • Defines the method getInstance , which enables other classes controlled access to the DictionaryService member variable service .

3. Implement the Service Provider

To provide this service, you must create a Dictionary.java implementation. To keep things simple, create a general dictionary that defines just a few words. You can implement the dictionary with a database, a set of property files, or any other technology. The easiest way to demonstrate the provider pattern is to include all the words and definitions within a single file.

The following code shows an implementation of the Dictionary SPI, the GeneralDictionary.java class. Notice that it provides a no-argument constructor and implements the getDefinition method defined by the SPI.

package dictionary;

import dictionary.spi.Dictionary;
import java.util.SortedMap;
import java.util.TreeMap;

public class GeneralDictionary implements Dictionary {

    private SortedMap<String, String> map;
    
    public GeneralDictionary() {
        map = new TreeMap<String, String>();
        map.put(
            "book",
            "a set of written or printed pages, usually bound with " +
                "a protective cover");
        map.put(
            "editor",
            "a person who edits");
    }

    @Override
    public String getDefinition(String word) {
        return map.get(word);
    }

}

The sample stores the compiled class file in the directory GeneralDictionary/build . Note : You must compile the classes dictionary.DictionaryService and dictionary.spi.Dictionary before the class GeneralDictionary .

The GeneralDictionary provider for this example defines just two words: book and editor. Obviously, a more usable dictionary would provide a more substantial list of generally used vocabulary.

To demonstrate how multiple providers can implement the same SPI, the following code shows yet another possible provider. The ExtendedDictionary.java service provider is an extended dictionary containing technical terms familiar to most software developers.

package dictionary;

import dictionary.spi.Dictionary;
import java.util.SortedMap;
import java.util.TreeMap;

public class ExtendedDictionary implements Dictionary {

        private SortedMap<String, String> map;

    public ExtendedDictionary() {
        map = new TreeMap<String, String>();
        map.put(
            "xml",
            "a document standard often used in web services, among other " +
                "things");
        map.put(
            "REST",
            "an architecture style for creating, reading, updating, " +
                "and deleting data that attempts to use the common " +
                "vocabulary of the HTTP protocol; Representational State " +
                "Transfer");
    }

    @Override
    public String getDefinition(String word) {
        return map.get(word);
    }

}

The sample stores the compiled class file in the directory ExtendedDictionary/build . Note : You must compile the classes dictionary.DictionaryService and dictionary.spi.Dictionary before the class ExtendedDictionary .

It is easy to imagine customers using a complete set of Dictionary providers for their own special needs. The service loader API enables them to add new dictionaries to their application as their needs or preferences change. Because the underlying word-processor application is extensible, no additional coding is required for customers to use the new providers.

4. Register Service Providers

To register your service provider, you create a provider configuration file, which is stored in the META-INF/services directory of the service provider's JAR file. The name of the configuration file is the fully qualified class name of the service provider, in which each component of the name is separated by a period ( . ), and nested classes are separated by a dollar sign ( $ ).

The provider configuration file contains the fully qualified class names of your service providers, one name per line. The file must be UTF-8 encoded. Additionally, you can include comments in the file by beginning the comment line with the number sign ( # ).

For example, to register the service provider GeneralDictionary create a text file named dictionary.spi.Dictionary . This file contains one line:

dictionary.GeneralDictionary

Similarly, to register the service provider ExtendedDictionary create a text file named dictionary.spi.Dictionary . This file contains one line:

dictionary.ExtendedDictionary

5. Create a Client That Uses the Service and Service Providers

Because developing a full word-processor application is a significant undertaking, this tutorial provides a simpler application that uses the DictionaryService and Dictionary SPI. The DictionaryDemo sample searches for the words book , editor , xml , and REST words from any Dictionary providers on the class path and retrieves their definitions.

The following is the DictionaryDemo sample. It requests a definition of the target word from the DictionaryService instance, which passes the request to its known Dictionary providers.

package dictionary;

import dictionary.DictionaryService;

public class DictionaryDemo {

  public static void main(String[] args) {

    DictionaryService dictionary = DictionaryService.getInstance();
    System.out.println(DictionaryDemo.lookup(dictionary, "book"));
    System.out.println(DictionaryDemo.lookup(dictionary, "editor"));
    System.out.println(DictionaryDemo.lookup(dictionary, "xml"));
    System.out.println(DictionaryDemo.lookup(dictionary, "REST"));
  }

  public static String lookup(DictionaryService dictionary, String word) {
    String outputString = word + ": ";
    String definition = dictionary.getDefinition(word);
    if (definition == null) {
      return outputString + "Cannot find definition for this word.";
    } else {
      return outputString + definition;
    }
  }
}

The sample stores the compiled class file in the directory DictionaryDemo/build . Note : You must compile the classes dictionary.DictionaryService and dictionary.spi.Dictionary before the class DictionaryDemo .

6. Package the Service Providers, the Service, and the Service Client in JAR Files

See the lesson Packaging Programs in JAR Files for information about how to create JAR files.

Packaging Service Providers in JAR Files

To package the GeneralDictionary service provider, create a JAR file named GeneralDictionary/dist/GeneralDictionary.jar that contains the compiled class file of this service provider and the configuration file in the following directory structure:

  • META-INF

  • services

  • dictionary.spi.Dictionary

  • dictionary

  • GeneralDictionary.class

Similarly, to package the ExtendedDictionary service provider, create a JAR file named ExtendedDictionary/dist/ExtendedDictionary.jar that contains the compiled class file of this service provider and the configuration file in the following directory structure:

  • META-INF

  • services

  • dictionary.spi.Dictionary

  • dictionary

  • ExtendedDictionary.class

Note that the provider configuration file must be in the directory META-INF/services in the JAR file.

Packaging the Dictionary SPI and Dictionary Service in a JAR File

Create a JAR file named DictionaryServiceProvider/dist/DictionaryServiceProvider.jar that contains the following files:

  • dictionary

  • DictionaryService.class

    • spi
  • Dictionary.class

Packaging the Client in a JAR File

Create a JAR file named DictionaryDemo/dist/DictionaryDemo.jar that contains the following file:

  • dictionary

  • DictionaryDemo.class

7. Run the Client

The following command runs the DictionaryDemo sample with the GeneralDictionary service provider:

Linux and Solaris:

java -Djava.ext.dirs=../DictionaryServiceProvider/dist:../GeneralDictionary/dist -cp dist/DictionaryDemo.jar dictionary.DictionaryDemo

Windows:

java -Djava.ext.dirs=..\DictionaryServiceProvider\dist;..\GeneralDictionary\dist -cp dist\DictionaryDemo.jar dictionary.DictionaryDemo

When using this command, the following is assumed:

  • The current directory is DictionaryDemo .

  • The following JAR files exist:

  • DictionaryDemo/dist/DictionaryDemo.jar : Contains the DictionaryDemo class

    • DictionaryServiceProvider/dist/DictionaryServiceProvider.jar : Contains the Dictionary SPI and the DictionaryService class

    • GeneralDictionary/dist/GeneralDictionary.jar : Contains the GeneralDictionary service provider and configuration file

The command prints the following:

book: a set of written or printed pages, usually bound with a protective cover
editor: a person who edits
xml: Cannot find definition for this word.
REST: Cannot find definition for this word.

Suppose you run the following command and ExtendedDictionary/dist/ExtendedDictionary.jar exists:

Linux and Solaris:

java -Djava.ext.dirs=../DictionaryServiceProvider/dist:../ExtendedDictionary/dist -cp dist/DictionaryDemo.jar dictionary.DictionaryDemo

Windows:

java -Djava.ext.dirs=..\DictionaryServiceProvider\dist;..\ExtendedDictionary\dist -cp dist\DictionaryDemo.jar dictionary.DictionaryDemo

The command prints the following:

book: Cannot find definition for this word.
editor: Cannot find definition for this word.
xml: a document standard often used in web services, among other things
REST: an architecture style for creating, reading, updating, and deleting data that attempts to use the common vocabulary of the HTTP protocol; Representational State Transfer

The ServiceLoader Class

The java.util.ServiceLoader class helps you find, load, and use service providers. It searches for service providers on your application's class path or in your runtime environment's extensions directory. It loads them and enables your application to use the provider's APIs. If you add new providers to the class path or runtime extension directory, the ServiceLoader class finds them. If your application knows the provider interface, it can find and use different implementations of that interface. You can use the first loadable instance of the interface or iterate through all the available interfaces.

The ServiceLoader class is final, which means that you cannot make it a subclass or override its loading algorithms. You cannot, for example, change its algorithm to search for services from a different location.

From the perspective of the ServiceLoader class, all services have a single type, which is usually a single interface or abstract class. The provider itself contains one or more concrete classes that extend the service type with an implementation specific to its purpose. The ServiceLoader class requires that the single exposed provider type has a default constructor, which requires no arguments. This enables the ServiceLoader class to easily instantiate the service providers that it finds.

Providers are located and instantiated on demand. A service loader maintains a cache of the providers that were loaded. Each invocation of the loader's iterator method returns an iterator that first yields all of the elements of the cache, in instantiation order. The service loader then locates and instantiates any new providers, adding each one to the cache in turn. You can clear the provider cache with the reload method.

To create a loader for a specific class, provide the class itself to the load or loadInstalled method. You can use default class loaders or provide your own ClassLoader subclass.

The loadInstalled method searches the runtime environment's extension directory of installed runtime providers. The default extension location is your runtime environment's jre/lib/ext directory. You should use the extension location only for well-known, trusted providers because this location becomes part of the class path for all applications. In this article, providers do not use the extension directory but will instead depend on an application-specific class path.

Limitations of the ServiceLoader API

The ServiceLoader API is useful, but it has limitations. For example, it is impossible to derive a class from the ServiceLoader class, so you cannot modify its behavior. You can use custom ClassLoader subclasses to change how classes are found, but ServiceLoader itself cannot be extended. Also, the current ServiceLoader class cannot tell your application when new providers are available at runtime. Additionally, you cannot add change-listeners to the loader to find out whether a new provider was placed into an application-specific extension directory.

The public ServiceLoader API is available in Java SE 6. Although the loader service existed as early as JDK 1.3, the API was private and only available to internal Java runtime code.

Summary

Extensible applications provide service points that can be extended by service providers. The easiest way to create an extensible application is to use the ServiceLoader , which is available for Java SE 6 and later. Using this class, you can add provider implementations to the application class path to make new functionality available. The ServiceLoader class is final, so you cannot modify its abilities.