Sometimes you need to design an application in such a way that users (devs) can drop in their own implementation of a service. Generally this is done via interfaces. But how do we communicate to our application that a new implementation has been created and how do we know which implementation of the service to use? This is where the plugin architecture comes in. In this blog I’ll be summarizing the Java Tutorial for Creating Extensible Applications. Their example uses a Dictionary that allows to you look up the definition of a word. Let’s take a look at classes and what each one of them does.
Sample Code Explained
DictionaryDemo.class is the main class that uses the Dictionary to look up words. It’s simply the caller of the service.
public class DictionaryDemo {
public static void main(String[] args) {
DictionaryService dictionary = DictionaryService.getInstance();
String definition = dictionary.getDefinition("book");
}
}
Dictionary.class is the service provider interface (SPI). This is what the service knows about and what the service providers must implement.
public interface Dictionary {
public String getDefinition(String word);
}
DictionaryService.class is a singleton that uses the ServiceLoader to get the implementations of Dictionary.class and does the work of interacting with the actual implementation.
public class DictionaryService {
private static DictionaryService service;
private ServiceLoader 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 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;
}
}
Then you have GeneralDictionary.class and ExtendedDictionary.class which are two actual implementations of Dictionary. They are the service providers. I’m not going to show the code here because the whole point is that the exact implementation doesn’t matter!
Configuration
So let’s say you are the dev who wants to provide the implementation of Dictionary, how do you actually communicate to the application that you have created the implementation? You do this by registering your service provider. Do this by creating a configuration file that contains the fully qualified class name of your service provider implementation. The name of the file must match the fully qualified class name of the service provider interface. Then, the configuration file should be stored in the META-INF/services directory of the service provider’s JAR file.
For example, if you have created the GeneralDictionary implementation of Dictionary, then the config file name is dictionary.spi.Dictionary (since Dictionary lives in the dictionary.spi package). The file contains one line, dictionary.GeneralDictionary (since GeneralDictionary lives in the dictionary package). Finally, the implementation and config file need to be packaged in a jar that should be placed in the application’s classpath.