Java SPI Overview
Java SPI (Service Provider Interface) is a built-in mechnaism for discovering and loading service implementations dynamically. Instead of hardcoding specific implementation classes, SPI allows you to define a service interface and have multiple providers supply their own implementations. The ServiceLoader class, part of the JDK, loads these services at runtime based on configuration files.
SPI Rules
- Create a file under
META-INF/services/named after the fully qualified interface name. The file contains one or more lines, each specifying the fully qualified class name of an implementation. - Use
java.util.ServiceLoaderto load the implementations at runtime. - If the implementation classes are packaged in a JAR, that JAR must be on the classpath.
- Each implementation class must have a public no-argument constructor.
Common Usage Scenarios
JDBC
Before JDBC 4.0, developers had to explicitly load database drivers using Class.forName("driver.class.name"). Since JDBC 4, the driver discovery relies on SPI. Drivers declare themselves via the META-INF/services/java.sql.Driver file, allowing the DriverManager to locate and load them automatically.
Apache Commons Logging
This logging facade uses SPI to find the actual logging implementation. Providers place a file META-INF/services/org.apache.commons.logging.LogFactory that points to the factory class. Commons Logging reads that file to instantiaet the correct factory.
Code Example
1. Service Interface
package com.example.spi.service;
public interface Greeting {
String speak();
}
2. Implementations
ChineseGreeting.java
package com.example.spi.impl;
import com.example.spi.service.Greeting;
public class ChineseGreeting implements Greeting {
@Override
public String speak() {
return "Ni Hao";
}
}
EnglishGreeting.java
package com.example.spi.impl;
import com.example.spi.service.Greeting;
public class EnglishGreeting implements Greeting {
@Override
public String speak() {
return "Hello";
}
}
3. SPI Configuration File
Create the directory META-INF/services/ and inside it a file named com.example.spi.service.Greeting. The content:
com.example.spi.impl.ChineseGreeting
com.example.spi.impl.EnglishGreeting
4. Client Code (Loading the Service)
package com.example.client;
import com.example.spi.service.Greeting;
import java.util.ServiceLoader;
public class SpiClient {
public static void main(String[] args) {
ServiceLoader<Greeting> loader = ServiceLoader.load(Greeting.class);
for (Greeting greeting : loader) {
System.out.println(greeting.speak());
}
}
}
5. Output
Ni Hao
Hello
Extension Example
Suppose you want to add a Korean greeting. You create a new module:
KoreanGreeting.javaimplementingGreeting.- A
META-INF/services/com.example.spi.service.Greetingfile containingcom.example.spi.impl.korean.KoreanGreeting. - Package the module as a JAR and place it on the classpath.
The client code remains unchanged and will automatically detect the new implementation.