Understanding and Implementing Java SPI and Spring SPI
This article explains Java's built‑in Service Provider Interface (SPI) mechanism, demonstrates how to create interfaces, implementations, and configuration files, shows testing with ServiceLoader, and compares it to Spring's SPI implementation using spring.factories, including code examples and analysis of their advantages and limitations.
Java's Service Provider Interface (SPI) is a built‑in discovery mechanism that enables framework extensions and component replacement by loading implementations of a given interface from configuration files located in META-INF/services . It is widely used in frameworks such as Dubbo, Spring, Common‑Logging, and JDBC.
Java SPI Implementation
The java.util.ServiceLoader class scans the classpath and the META-INF/services directory for files named after the fully‑qualified interface name, reads the listed implementation class names, and loads them.
Example
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");
}
}Place a file named com.skywares.fw.juc.spi.VedioSPI under META-INF/services containing the fully‑qualified names of the implementations.
Test
public class VedioSPITest {
public static void main(String[] args) {
ServiceLoader
serviceLoader = ServiceLoader.load(VedioSPI.class);
serviceLoader.forEach(t -> t.call());
}
}The output shows both implementations being invoked.
Source Analysis
The ServiceLoader class implements Iterable and uses an internal LazyIterator to create iterator instances.
All service configuration files reside in META-INF/services , and the directory name is fixed.
Limitations of Java SPI include the need to iterate over all providers and the mandatory placement of configuration files in the services directory.
Spring SPI
Spring adopts the same design idea but extends it with spring.factories , allowing multiple implementations of different interfaces to be declared in a single file.
Spring Example
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");
}
}In META-INF/spring.factories add:
com.skywares.fw.juc.springspi.DataBaseSPI = com.skywares.fw.juc.springspi.DB2DataBase, com.skywares.fw.juc.springspi.MysqlDataBaseTest class:
public class SpringSPITest {
public static void main(String[] args) {
List
dataBaseSPIs = SpringFactoriesLoader.loadFactories(
DataBaseSPI.class, Thread.currentThread().getContextClassLoader());
for (DataBaseSPI db : dataBaseSPIs) {
db.getConnection();
}
}
}The result demonstrates that Spring's SPI loads both implementations via a single spring.factories file, simplifying configuration compared to Java's one‑file‑per‑interface approach.
Overall, Java SPI provides a solid foundation for service discovery, while Spring SPI builds on it with more flexible configuration and reduced boilerplate.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.