Service Provider Interface: Creating Extensible Java Applications
Most of an application's life cycle revolves around maintenance. An extensible application allows for easy maintenancei.e., upgrading of specific parts of a productwithout 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.