The ServiceLoader API (part of JDK 1.6) allows you to use the service provider interface (SPI) approach to 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:
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.
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.
Figure 2 shows a class diagram of the service provider for the Weather SPI.
The following are the classes involved in the service provider for Weather SPI:
- Weather: This class defines the SPI specification for weather service. It has two overloaded methods that define the behavior: retrieving the weather reading given the city name or zip code.
- WeatherReading: This auxiliary class supports the specification that has the return types defined.
- WeatherServiceProvider: This class provides the implementation logic for the Weather SPI. It maintains two different collections: one for storing city names and their related weather information and another for storing zip codes and their related weather information.
A provider may spread across multiple classes, and a more elaborate design may fill in the collections by using a web service to connect to the vendor site. But for demonstration purposes, this simple class should suffice. The following listing demonstrates the logic of the service provider for the Weather SPI:
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).
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.
Figure 4 shows a class diagram that describes the classes involved.
The WeatherService class follows the Singleton pattern. It implements the Weather SPI specification to adopt the Weather SPI but delegates the calls to retrieve the weather info to the actual provider. Thus, it acts as representative/proxy to the service provider WeatherServiceProvider. It uses the ServiceLoader API to load the provider(s) (more than one provider could exist for the same SPI specification). By iterating through the cache of the loader, it picks up the first loaded and instantiated provider. This approach provides a level of indirection between the providers and users and thus accomplishes loose coupling between the concrete providers and the users. The following code snippet demonstrates the iteration logic:
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.
"Creating Extensible Applications With the Java Platform" by John OConner (from java.sun.com)
The weather example was taken from Apache and java.sun.com.
"ServiceLoader API" (from java.sun.com)
About the AuthorThribhuvan Thakur
is a senior software engineer and Technical Lead at Southwest Airlines with 12 years of experience in design and development in J2EE. He has been working with Java since its inception, and with JMS since 1999. Thakur is a Sun Certified Java Programmer, Sun Certified Java Developer, and Sun Certified J2EE Architect. He holds a Masters in Computer Science from the University of North Texas.