Java Service Provider Interface (SPI)

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:

  1. create a contract (i.e. project containing interface)
  2. create a provider that implements the contract and publish implementation through SPI service
  3. 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

pom fragment
  <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;
   }

}
 

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.