Java Service Provider Interface (SPI)— understanding it via code

Nitesh Agrawal
ITNEXT
Published in
4 min readMay 15, 2018

--

Ever thought how that JPA implementation (EclipseLink or Hibernate) is picked up by the Java Persistence APIs. Or How does java.sql.DriverManager loads the available implementation of java.sql.Driver. Or for that matter How JMS implementation is picked up by messaging framework.

Well they all follow JAVA SPI (Service Provider Interface) mechanism.

According to Effective Java, 2nd Edition :

A service provider framework is a system in which multiple service providers implement a service, and the system makes the implementations available to its clients, decoupling them from the implementations.

There are 3 essential components of a service provider framework:

  1. A service interface, which providers implement.
  2. A provider registration API, which the system uses to register implementations, giving clients access to them.
  3. and A service access API, which client chooses to obtain an Instance of the service.

In this article we will go through a sample code, through which we will try to understand Java Service Provider framework.

Set Up :

All the code is available @ https://github.com/niteshagrawalgmail/basic.git in the folder spi.

https://github.com/niteshagrawalgmail/basic/tree/master/spi

  1. A maven web project [basicapp] which will act as a consumer/client of the Service Interface. This project will have dependency on Service Interface and its implementations and will be assembled as a .war which will be deployed on Tomcat server.
  2. A maven project having Service Interface.[com.niaa.service.api]
  3. Two maven projects representing 2 provider implementation of the service interface. [com.niaa.service.impl1] and [com.niaa.service.impl2]

Service Interface

The project com.niaa.service.api contains a interface IAccount.

package com.niaa.service.api.accoumt;public interface IAccount {  public String getAccountType();  public String getAccountId();}

Service Provider Implementation

The project com.niaa.service.impl1 contains implementation of IAccount.

package com.niaa.service.impl.savings.account;import com.niaa.service.api.accoumt.IAccount;public class SavingsAccount implements IAccount {public String getAccountType() {  return "SavingsAccount";}public String getAccountId() {  return "1";}}

The class SavingsAccount implements IAccount and return “SavingsAccount” as account type.

In order to hook in to the Service Provider Framework, we need to create a file with the name : com.niaa.service.api.accoumt.IAccount in the folder META-INF/services/

inside the file (content of the file) we need to provide fully qualified name of the Provider Implementation.

com.niaa.service.impl.savings.account.SavingsAccount
folder structure of provider implementation project

Similarly the project com.niaa.service.impl2 contains one more implementation of IAccount.

package com.niaa.service.impl.current.account;import com.niaa.service.api.accoumt.IAccount;public class CurrentAccount implements IAccount {public String getAccountType() {  return "CurrentAccount";}public String getAccountId() {  return "2";}}

The class CurrentAccount implements IAccount and return “CurrentAccount” as account type.

In order to hook in to the Service Provider Framework, we need to create a file with the name : com.niaa.service.api.accoumt.IAccount in the folder META-INF/services/

inside the file (content of the file) we need to provide fully qualified name of the Provider Implementation.

com.niaa.service.impl.current.account.CurrentAccount
folder structure of second provider implementation project

Now we have 2 implementations of IAccount, SavingsAccount and CurrentAccount. Both provide the required configuration file com.niaa.service.api.accoumt.IAccount and contain the required provider implementation class name in the respective files.

Next we will see, how Service Access API loads these implementation instances.

Now, in our consumer project [basicapp], we have a HttpServlet called E2eServlet. This project assembles all the other project — Service Interface + its provider implementations.

pom.xml of basicapp :

pom.xml of basicapp

doGet(HttpServletRequest request, HttpServletResponse response) method of the E2eServlet :

Here we are using java.util.ServiceLoader API of service provider framework.

We have created transfer object called AccountTO which is used to serialize aggregated accounts as JSON to response.

When we fire the API :

http://localhost:8081/basicapp/api

[
{"accountId":"1",
"accountType":"SavingsAccount"
},
{"accountId":"2",
"accountType":"CurrentAccount"
}
]

Summary :

Service Provider framework provides an easy way to decouple and load multiple implementations of a Service Interface.

The only important thing is to make sure the necessary configuration file is available in the folder [META-INF/services] as expected by the framework.

Also make a note about the naming convention of the configuration file.

Thanks for reading :)

--

--