Understanding Java SPI and Spring SPI Mechanisms
This article explains the Java Service Provider Interface (SPI) concept, demonstrates how to implement and test SPI with ServiceLoader, compares its limitations, and shows how Spring extends SPI using spring.factories for more flexible and efficient service loading.
SPI (Service Provider Interface) is a built‑in Java mechanism that enables framework extensions and component replacement by discovering implementations of a given interface at runtime. It is widely used in frameworks such as Dubbo, Spring, Common‑Logging, and JDBC.
Java implements SPI through the java.util.ServiceLoader class, which scans the META-INF/services/ directory on the classpath for files named after the fully‑qualified interface name and loads the listed implementation classes.
Example – Java SPI
public interface VedioSPI {
void call();
}
public class Mp3Vedio implements VedioSPI {
@Override
public void call() {
System.out.println("this is mp3 call");
}
}
public class Mp4Vedio implements VedioSPI {
@Override
public void call() {
System.out.println("this is mp4 call");
}
}
// META-INF/services/com.example.VedioSPI contains:
// com.example.Mp3Vedio
// com.example.Mp4VedioTest code using ServiceLoader:
public class VedioSPITest {
public static void main(String[] args) {
ServiceLoader
loader = ServiceLoader.load(VedioSPI.class);
loader.forEach(VedioSPI::call);
}
}The output shows both implementations being invoked. The underlying principle is that ServiceLoader implements Iterable , creates an internal LazyIterator , and iterates over the discovered providers.
Limitations of Java SPI include the need for a separate configuration file per interface and reliance on full classpath scanning, which can be inefficient.
Spring SPI
Spring adopts the same idea but uses a single spring.factories file to map multiple interfaces to their implementations, allowing easier management and additional optimizations.
Example – Spring SPI:
public interface DataBaseSPI {
void getConnection();
}
public class DB2DataBase implements DataBaseSPI {
@Override
public void getConnection() {
System.out.println("this database is db2");
}
}
public class MysqlDataBase implements DataBaseSPI {
@Override
public void getConnection() {
System.out.println("this is mysql database");
}
}
// META-INF/spring.factories entry:
// com.example.DataBaseSPI=com.example.DB2DataBase,com.example.MysqlDataBaseTest code using Spring's SpringFactoriesLoader :
public class SpringSPITest {
public static void main(String[] args) {
List
list = SpringFactoriesLoader.loadFactories(
DataBaseSPI.class, Thread.currentThread().getContextClassLoader());
for (DataBaseSPI spi : list) {
spi.getConnection();
}
}
}Spring’s approach reduces the number of configuration files, supports multiple implementations per interface via comma separation, and leverages lazy loading and caching for better performance.
In summary, both Java and Spring provide SPI mechanisms to decouple service interfaces from their implementations, enhancing modularity and extensibility; Spring further refines the process with a more compact configuration format and runtime optimizations.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.