MultiSPI - consuming service provider interfaces in 2011
Implementing a Java SPI isn't a particularly 2011 experience1. Creating a correctly-named text file in META-INF/services, making sure it is correctly packaged and remembering to keep it up to date when you refactor is sufficiently annoying and error-prone that there are at least a couple of utils that aim to make this easier.
At XebiaLabs, however, we're not just the implementors of our plugin SPI. We also write the deployment engine that consumes these plugins. And unfortunately, there isn't much out there to help you read, load and verify services. Hence MultiSPI.
What about ServiceLoader?
Evidently, the engineers at
SunOracle - as prominent SPI consumers - shared this frustration. In Java 6, they introduced ServiceLoader to at least make "vanilla" SPI consumption easier. MultiSPI isn't just a fully-compatible alternative for those still working with Java 5, however. It also supports a number of common additional use cases:
- Inspecting the list of class names before they are loaded, for instance to remove or replace some items (think user-definable overrides), to use different class loaders for different packages (think OSGi) etc.
- Inspecting the classes before they are instantiated, for instance to check for required annotations or implemented interfaces, to delay creation of instances etc.
- Using non-vanilla instantiation strategies, for instance to support factory methods, or in conjunction with dependency injection etc.
Providing services in 2011
More importantly, though, MultiSPI doesn't just support reading service implementations from META-INF/services files. Because let's face it, if you're designing a component-provider mechanism today, it probably won't be based on the vanilla SPI specification.
MultiSPI supports annotation- and manifest-scanning out-of-the-box, but you can add providers for any scheme of your choice, be it XML descriptors, system properties, environment variables or whatever. Of course, you can also mix and match different providers, especially useful if (as in our case with Labs) you are supporting multiple versions of an SPI.
SPIs and dependency injection
Ultimately, of course, SPIs are just service factories, and if you're working within a dependency injection context you probably don't want to invoke an SPI loader explicitly, you just want the resulting services to "be available" for injection into your business logic.
Indeed, Spring added a ServiceLoader-backed factory in 2.5, and of course you can do the same with MultiSPI, whether you want the implementation class names, the classes or the instances themselves. There's Spring and Guice examples; here's the Spring context that prepares a set of class names, classes and instances, for example:
<bean id="multiSpi" class="com.qrmedia.commons.multispi.MultiSpi"> <constructor-arg> <set> <bean class="com.qrmedia.commons.multispi.provider.MetaInfServicesProvider" /> <bean class="com.qrmedia.commons.multispi.provider.AnnotationScanningProvider"> <constructor-arg value="uk.gov.mi6.LicenseToKill" /> <constructor-arg value="uk.gov" /> </bean> </set> </constructor-arg> </bean> <bean id="agentNames" factory-bean="multiSpi" factory-method="findImplementationNames"> <constructor-arg value="uk.gov.mi6.Agent" /> </bean> <bean id="agentClasses" factory-bean="multiSpi" factory-method="findImplementations"> <constructor-arg value="uk.gov.mi6.Agent" /> </bean> <bean id="agents" factory-bean="multiSpi" factory-method="loadImplementations"> <constructor-arg value="uk.gov.mi6.Agent" /> </bean>
Of course you can use many different MultiSpi instances, with different provider configurations, in your application at the same time.
To use MultiSPI, just add the following dependency and repositories to your POM:
<dependency> <groupId>com.qrmedia.commons</groupId> <artifactId>multi-spi</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <repository> <id>qrmedia-releases</id> <url>http://aphillips.googlecode.com/svn/maven-repository/releases</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>qrmedia-snapshots</id> <url>http://aphillips.googlecode.com/svn/maven-repository/snapshots</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository>
If you think you might run into conflicting dependencies and want something that just works without having to deal with <excludes>, you can use:
<dependency> <groupId>com.qrmedia.commons</groupId> <artifactId>multi-spi</artifactId> <version>1.0-SNAPSHOT</version> <classifier>jar-with-dependencies</classifier> </dependency>
The Maven site and project reports are here.