The architecture of most Java (and not only) applications today provides for the possibility of expanding the functionality through various kinds of
magic effects on the code. Recently, this has also become possible if you use some trendy
framework or IoC container . But what to do if the application is long-lived and too complex to translate it to use any framework?
In the last application with which I worked, the unknown to me was the SPI
bike , which looked for text files like
META-INF / services / <qualified interface name> in jark and took from there the name of the required class that implements this interface, further This class was used as an extension. Searching on the Internet, I learned that the
Service Provider Interface (SPI) is a software mechanism for supporting plug-in components and that this mechanism has been used for a long time in the
Java Runtime Environment (JRE) , for example, in
Java Database Connectivity (JDBC) :
ps = Service.providers(java.sql.Driver.class); try { while (ps.hasNext()) { ps.next(); } } catch (Throwable t) {
Thanks to this code, applications no longer need the
Class.forName (<driver class>) construction (although they will work with it), JDBC drivers will be loaded automatically when you first access the methods of the
DriverManager class.
The SPI mechanism is also used in
Java Cryptography Extension (JCE) ,
Java Naming and Directory Service (JNDI) ,
Java API for XML Processing (JAXP) ,
Java Business Integration (JBI) , Java Sound, Java Image I / O.
')
How it works?
The whole point is in the separation of logic into service (Service) and providers (Service Providers). Links to providers are stored in the extensions jarks in a text file (UTF-8)
META-INF / services / <qualified service class> , in each line the full name of the provider class. Blank lines and comments (starting with a # character) are ignored. Restrictions on providers: they must implement the interface or inherit from the class of service and have a default constructor (zero-argument public constructor).
The main application can use the
java.util.ServiceLoader utility, which is part of the Java SE 6 API, to get the list of providers, which works according to the following principle:

The user code requests the configuration loader for a particular service, the loader loads the providers from the configuration as needed and stores them in the cache. It is also possible to clear the cache and reload the configuration.
In earlier versions of Java SE, there is a similar utility,
sun.misc.Service , that works on the same principle, but is part of the proprietary
Sun Oracle software and can be removed in future releases of Java SE.
Usage example
For example, we have a program that searches for music on a computer and displays the result sorted by name on the screen.
public class MusicFinder { public static List<String> getMusic() {
At some point in time, we realized the importance of this program for the society and decided to share it with our friends. Friends used the service and decided that something was missing. Can output to a separate file? But then you have to rewrite all this cool code. Not necessary, you can use the SPI mechanism.
For example, create a plugin for our super program:
public class FileReportRenderer extends ReportRenderer { @Override public void generateReport() { final List<String> music = findMusic(); try { final FileWriter writer = new FileWriter("music.txt"); for (String composition : music) { writer.append(composition); writer.append("\n"); } writer.flush(); } catch (IOException e) { e.printStackTrace(); } } }
Put the following in
META-INF / services / com.example.ReportRenderer :
com.example.FileReportRenderer
Let's make the initial program expandable:
public class ReportRenderer {
When you start the application, as before, will display all the music found on the screen. But if we put the extension jark that we just created in the
classpath , we will end up with a music.txt
file containing the search results.
Now it's time to play
around with
MusicFinder . Let's make it expandable too. To do this, change the class to the interface:
public interface MusicFinder { List<String> getMusic(); }
Add the implementation in the main module:
public class DummyMusicFinder implements MusicFinder { public List<String> getMusic() { return Collections.singletonList("From DummyMusicFinder..."); } }
Extensions support in
ReportRenderer :
public class ReportRenderer {
As in the case of
ReportRenderer, add the text file
META-INF / services / com.example.MusicFinder , containing:
com.example.DummyMusicFinder
Again, the result of the first program has not changed. Now the extension. Here we make two
MusicFinder implementations:
public class ExtendedMusicFinder implements MusicFinder { public List<String> getMusic() { return Collections.singletonList("From ExtendedMusicFinder..."); } } public class MyMusicFinder implements MusicFinder { public List<String> getMusic() { return Collections.singletonList("From MyMusicFinder..."); } }
META-INF / service / com.example.MusicFinder :
com.example.MyMusicFinder
com.example.ExtendedMusicFinder
Well, that's all, the program that supports the extension is ready, now with the extension in the
classpath , it will give the list
From DummyMusicFinder ...
From ExtendedMusicFinder ...
From MyMusicFinder ...
Sample sources can be found
here .
Conclusion
This example is far from perfect, and I do not pretend to be the author of the world's coolest music search engine. I also do not call for fanatical use of this mechanism, since it is not applicable everywhere, and I consider using the IoC container as a more beautiful solution, but still, in some places and for some, this approach may be useful. Thanks for taking the time to read this article.
Literature
PluginService Provider InterfaceService ProviderService Provider Interface: Creating Extensible Java ApplicationsService loader