Most of an application’s life cycle revolves around maintenance. An extensible application allows for easy maintenance—i.e., upgrading of specific parts of a product—without affecting the entire application. This article examines the service provider interface (SPI) approach to creating extensible applications, a key to modular, plug-in architectures.
The article begins by defining what a service provider interface is, how it is implemented, and how you can achieve extensibility by using the ServiceLoader API (part of JDK 1.6).
An application is an aggregation of cohesive services. While an application offers a broader set of functionality in terms of application programming interfaces (APIs) and classes, a service provides access to some specific application functionality or feature. The service defines the interfaces for the functionality and a way to retrieve an implementation. For example, consider an application that provides a variety of information about a geographical location, such as real estate data, weather information, demographics, etc. The weather service, a part of the application, may define only the interface for retrieving the weather information (more on this example shortly).
A service provider interface (SPI) is the set of public interfaces and abstract classes that a service defines. A SPI may be represented by a single interface (type) or abstract class or a set of interfaces or abstract classes that define the service contract.
The java.util.spi package in JDK 1.6 defines some examples of a SPI:
- CurrencyNameProvider is an abstract class (service contract) for service providers that provide localized currency symbols for the Currency class.
- TimeZoneNameProvider is an abstract class for service providers that provide localized time zone names for the TimeZone class.
A service provider implements the SPI and contains one or more concrete classes that implement or extend (subclass) the service type. A single SPI specification can have more than one provider. To promote loose coupling and information hiding, the provider class is typically not the entire provider itself but rather a proxy that contains enough functionality to decide whether the provider is able to satisfy a particular request.
Installing and Loading Service Providers
You can install service providers by simply adding a new Java Archive (JAR) file that holds the provider classes to the application’s classpath or by placing the JAR into any of the usual extension directories (jre/lib/ext).
You identify a service provider by placing a provider-configuration file in the resource directory META-INF/services. The file’s name is the fully qualified binary name of the service’s type. The file contains a list of fully qualified binary names of concrete provider classes, one per line. For information about file specification, look at the java.util.ServiceLoader documentation.
The java.util.ServiceLoader, part of the Java SE 6 API, is a simple service-providing loading facility for finding, loading, and using service providers. It maintains a cache of the providers that have been loaded. The only requirement this facility enforces is that every provider class must have a zero-argument constructor, so that it can be instantiated during loading. Providers are located and instantiated on demand. Figure 1 best illustrates the relationship between the service providers and service loader.
Figure 1. Relationship Between Service Providers and Service Loader: This diagram best illustrates the relationship between the service providers and service loader.
Following are the important methods of the ServiceLoader class:
- load(): This method is a factory method for creating an instance of ServiceLoader. It comes in two flavors: one that takes the class name of the service class only and another that also takes ClassLoader. The reference to the classloader comes from either the currently executing thread or the class object. The following example loads the Weather service:
- loadInstalled(): This method loads only the installed providers from the JVM’s extension directory (jre/lib/ext), ignoring the providers on the application’s class path. The following code fragment prints the sun.net.spi.nameservice.NameServiceDescriptor that comes with JDK 1.6:
- iterator():This method yields the cache of the providers that have been loaded, in instantiation order. It then lazily locates and instantiates any remaining providers, adding each one to the cache in turn. It doesn’t support removal.
- reload():This method clears the loader’s provider cache so that all providers will be reloaded. This is useful when new providers need to be installed into a running JVM.
A SPI Case Study
This section demonstrates a service provider for retrieving weather-related information defined by a Weather SPI and the load() and iterators() methods of the ServiceLoader API. The weather service has a single goal: retrieving weather-related information such as temperature and rainfall for a given location, given the city name or zip code.
Registering the Service Provider
Now that the service provider is complete, it’s time to register it via the configuration file under META-INF-/services. You can use the following process to do so. (Note that the file name is the name of the specification class. The contents of the file reflect the concrete name of the service provider class that implements the SPI specification.)
- Create a directory META-INF/services in the project, if it does not exist already.
- Create the configuration file com.weather.spi.Weather in the project under META/Services.
- Add the contents com.weather.spi.WeatherServiceProvider to the file com.weather.spi.Weather as a single-line listing.
- JAR up the contents as shown in Figure 3 below. (You could use the export feature in Eclipse to create and save the JAR file).
Figure 3. JAR for Service Provider Registration: Here is how to register the service provider via the configuration file under META-INF-/services.
Design Patterns That Support SPI
A pattern is a standard or reusable solution to a common problem, and a design pattern is a template for the relationships and interactions between classes or objects. A design pattern hints at different implementation possibilities while providing a general solution to a problem. A design pattern needs to be translated into code and could be implemented in several different ways based upon the situation.
In the Singleton pattern, only a single instance of a class is ever created. From a design perspective, only a single class or object assumes the responsibility for insuring that a Singleton is created only once.
The considerations for creating Singletons in Java include:
- Declare the single instance of the class as private and static.
- Hide the constructor of a Singleton by declaring private.
- The constructor will initialize the single instance.
- Provide a global point of access to the single instance via getInstance() or an equivalent.
- Synchronize the global point of access to make it thread safe.
The above techniques apply to the simplest form of creating a Singleton in a non-cluster environment. If the constructor needs parameters, you add a static factory method such as createInstance(ParameterList…). If you decide not to synchronize the getInstance(), refer to more detailed techniques such as double-checked locking pattern or extending a Singleton.
The Service Provider Demo
This section demonstrates how users would load and use the service provider. To use the Weather service provider, you need to add the provider JAR to the application classpath. If you are using Eclipse, you could add the provider JAR to the Build Path.
If you were to create a new provider for the Weather SPI, all you’d need to do is follow the pattern for WeatherServiceProvider:
- Finish coding the new Weather provider.
- Add the name of the concrete class as the contents to the binary file (com.weather.spi.Weather) to reflect the new concrete class.
- Create a new JAR and add it to the application classpath.
The code that used the providers will remain unchanged. The ServiceLoader API allows users to add new implementations to their applications as they become available, thus paving the way to extensibility and modularity.
The following code snippet demonstrates how to use WeatherService:
You now have completed a demonstration of a service provider that fulfills the SPI specification. You have also seen how to use the ServiceLoader API as an approach for creating extensible applications. SPI is a starting place for building extensible applications. It could even be extended to support more recent concepts such as the service-oriented architecture using web services. For instance, you could add translation logic to WeatherService to convert the return response of getCurrentWeather() into XML using JAXB.