Introduction
When we use an approach service based we have to deal with interfaces (i.e. contracts), factories and implementations. We need a mechanism to discovery the right implementation for specific service. A commons solution is to use a system property that contain the full qualified name (FQN) of the implementation class and then the factory will use this to instantiate the implementation. Service Provider Interface allow us to use a standard technique to discovery a right implementation on a desired interface (i.e. contract )
SPI
This technique consist of put a file, whose name is contract's name , within jar under path META-INF/services. For example if the interface is org.bsc.spi.MyContract into the jar the file will be:
META-INF/services/org.bsc.spi.MyContract
and within this file must be put just one line containing the FQN of the implementation.
At this point the steps to create a SPI application will be:
- create a contract (i.e. project containing interface)
- create a provider that implements the contract and publish implementation through SPI service
- create a factory (consumer) that discovery implementation and return the instance
Java Example
Create a contract
package org.bsc.spi; public interface MyContract { String test(); }
Create a provider
As provider we mean an implementation of a contract. See example below:
public class MyProvider implements MyContract { public String test() { return "hello world!"; } }
To publish this provider through the SPI we have found a very useful project https://metainf-services.dev.java.net/ that allow us to write the SPI infos simply using the annotation @MetaInfServices. See example below
@MetaInfServices public class MyProvider implements MyContract { public String test() { return "hello world!"; } }
to use this project from maven you have to put the following dependency in your pom
<repositories> <repository> <id>java.net</id> <url>http://download.java.net/maven/2/</url> </repository> </repositories> <dependencies> <dependency> <groupId>org.kohsuke.metainf-services</groupId> <artifactId>metainf-services</artifactId> <version>1.1</version> <optional>true</optional> <type>jar</type> </dependency>
Note: optional means that this dependency will be not include in project that refer to your provider project. Run clean & install (or package) and automatically the SPI information will be included in your jar
Create a Factory
Finally we can develop the factory that discover provider and instantiate it
public class MyContractFactory { public static MyContract createInstance() throws Exception { java.io.InputStream is = MyContractFactory.class.getClassLoader().getResourceAsStream("META-INF/services/org.bsc.spi.MyContract"); java.io.LineNumberReader r = new LineNumberReader( new java.io.InputStreamReader(is)); String fqn = r.readLine(); return (MyContract)Class.forName(fqn).newInstance(); } }
public class MyContractFactory { public static List<MyContract> createInstance() throws Exception { java.util.List<MyContract> result = new java.util.ArrayList<MyContract>(); Enumeration<URL> e = Consumer.class.getClassLoader().getResources("META-INF/services/org.bsc.spi.MyContract"); while( e.hasMoreElements() ) { URL url = e.nextElement(); java.io.LineNumberReader r = new LineNumberReader( new java.io.InputStreamReader(url.openStream())); String fqn = r.readLine(); result.add( (MyContract)Class.forName(fqn).newInstance() ); } return result; } }
Comments (2)
Dec 16, 2009
Francesco Falanga says:
Grande Bartolo! nelle mie factory già usavo Class.forName(...., ma non avre...Grande Bartolo!
nelle mie factory già usavo Class.forName(...., ma non avrei mai pensato ad un sistema di discovery (modello WS).
Dec 17, 2009
Bartolomeo Sorrentino says:
thanks for feedback moreover this discovery system allow to find service in dif...thanks for feedback
moreover this discovery system allow to find service in different jars
a first step to introduce componentization