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.
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:
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.
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>
instead.
The Maven site and project reports are here.
Tags: dependency injection, Java, spi
Filed under Java, Tools | No Comments »