Using the SPI engine to create extensions
The architecture of most Java (and not only) applications today provides for the possibility of expanding functionality through various kinds of magical effects on the code. Recently, it has also become possible if you use some kind of fashionable framework or IoC container . But what if the application is long-lived and too complex to translate it to use any framework?
In the last application that I worked with, at that time an unknown SPIbike mechanism was implemented , which looked in text files for META-INF / services / and from there took the name of the desired class that implements this interface, then this class was used as an extension. After searching the Internet, I found out that the Service Provider Interface (SPI) is a software mechanism for supporting plug-in components and that this mechanism has been used for quite some time in the Java Runtime Environment (JRE) , for example, in Java Database Connectivity (JDBC) :
Thanks to this code, applications no longer need the construction of Class.forName () (although they will work with it), JDBC drivers will be loaded automatically the first time you 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.
The whole point is the separation of logic into service (Service) and providers (Service Providers). Links to providers are stored in extension jars in a text file (UTF-8)META-INF / services / , in each line, the fully qualified class name of the provider. Blank lines and comments (starting with the # character) are ignored. Restrictions on providers: they must implement the interface or inherit from the service class and have a default constructor (zero-argument public constructor).
The main application for obtaining a list of providers can use the java.util.ServiceLoader utility , which is part of the Java SE 6 API , which works as follows: User code requests a configuration loader for a particular service, the loader loads providers from the configuration as needed and saves them in the cache . It is also possible to clear the cache and reload the configuration. Earlier versions of Java SE have a similar utility.

sun.misc.Service , works on the same principle, but is part ofSun Oracle's proprietary software and may be removed in future Java SE releases.
For example, we have a program that searches for music on a computer and displays the result sorted by name on the screen.
At some point in time, we realized the importance of this program for 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. No need, you can use the SPI mechanism.
For example, create a plugin for our super program:
Put the following in META-INF / services / com.example.ReportRenderer :
Let's make the source program extensible:
At startup, the application, as before, will display all the music found on the screen. But if we put the newly created extension jar 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 extensible too. To do this, change the class to the interface:
Add the implementation in the main module:
Extension support in ReportRenderer :
As in the case of ReportRenderer, add the text file META-INF / services / com.example.MusicFinder , containing:
Again, the result of the first program has not changed. Now the extension. Here we will make two implementations of MusicFinder :
META-INF / service / com.example.MusicFinder :
Well, that’s all, the program supporting the extensions is ready, now with the extension in the classpath , it will list:
Sample sources can be found here .
The above 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 the fanatical use of this mechanism, since it is not applicable everywhere, and I consider the use of an IoC container to be a more beautiful solution, but still this approach may be useful in some places. Thank you for taking the time to read the article.
Plug-in
the Service Provider Interface
the Service Provider
the Service Provider Interface: Creating Company the Extensible the Java the Applications
the Service the Loader
In the last application that I worked with, at that time an unknown SPI
ps = Service.providers(java.sql.Driver.class); try { while (ps.hasNext()) { ps.next(); } } catch (Throwable t) { // Do nothing }
Thanks to this code, applications no longer need the construction of Class.forName (
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 the separation of logic into service (Service) and providers (Service Providers). Links to providers are stored in extension jars in a text file (UTF-8)
The main application for obtaining a list of providers can use the java.util.ServiceLoader utility , which is part of the Java SE 6 API , which works as follows: User code requests a configuration loader for a particular service, the loader loads providers from the configuration as needed and saves them in the cache . It is also possible to clear the cache and reload the configuration. Earlier versions of Java SE have a similar utility.

sun.misc.Service , works on the same principle, but is part of
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 ListgetMusic() { //some code } } public class ReportRenderer { public void generateReport() { final List music = findMusic(); for (String composition : music) { System.out.println(composition); } } public List findMusic() { final List music = MusicFinder.getMusic(); Collections.sort(music); return music; } public static ReportRenderer getInstance() { return new ReportRenderer(); } public static void main(final String[] args) { final ReportRenderer renderer = ReportRenderer.getInstance(); renderer.generateReport(); } }
At some point in time, we realized the importance of this program for 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. No need, 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 Listmusic = 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 source program extensible:
public class ReportRenderer { //... public static ReportRenderer getInstance() { final Iteratorproviders = ServiceLoader.load(ReportRenderer.class).iterator(); if (providers.hasNext()) { return providers.next(); } return new ReportRenderer(); } //... }
At startup, the application, as before, will display all the music found on the screen. But if we put the newly created extension jar 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 extensible too. To do this, change the class to the interface:
public interface MusicFinder { ListgetMusic(); }
Add the implementation in the main module:
public class DummyMusicFinder implements MusicFinder { public ListgetMusic() { return Collections.singletonList("From DummyMusicFinder..."); } }
Extension support in ReportRenderer :
public class ReportRenderer { //... public ListfindMusic() { final List music = new ArrayList (); for (final MusicFinder finder : ServiceLoader.load(MusicFinder.class)) { music.addAll(finder.getMusic()); } Collections.sort(music); return music; } //... }
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 will make two implementations of MusicFinder :
public class ExtendedMusicFinder implements MusicFinder { public ListgetMusic() { return Collections.singletonList("From ExtendedMusicFinder..."); } } public class MyMusicFinder implements MusicFinder { public List getMusic() { return Collections.singletonList("From MyMusicFinder..."); } }
META-INF / service / com.example.MusicFinder :
com.example.MyMusicFinder com.example.ExtendedMusicFinder
Well, that’s all, the program supporting the extensions is ready, now with the extension in the classpath , it will list:
From DummyMusicFinder ... From ExtendedMusicFinder ... From MyMusicFinder ...
Sample sources can be found here .
Conclusion
The above 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 the fanatical use of this mechanism, since it is not applicable everywhere, and I consider the use of an IoC container to be a more beautiful solution, but still this approach may be useful in some places. Thank you for taking the time to read the article.
Literature
Plug-in
the Service Provider Interface
the Service Provider
the Service Provider Interface: Creating Company the Extensible the Java the Applications
the Service the Loader