Understanding Java SPI and Spring SPI: Implementation, Examples, and Source Code Analysis
This article explains Java's built‑in Service Provider Interface (SPI) mechanism, demonstrates how to implement and test SPI with sample code, analyzes its internal workings and limitations, and then shows how Spring extends SPI using spring.factories with comparable examples and source‑code insights.
SPI (Service Provider Interface) is a built‑in Java mechanism for discovering service implementations, widely used in frameworks such as Dubbo, Spring, Common‑Logging, and JDBC to enhance extensibility.
Java SPI Implementation
Java's SPI uses java.util.ServiceLoader to parse the META-INF/services/ directory on the classpath, loading classes listed in files named after the fully‑qualified interface name.
Example
Define Dynamic Interface
public interface VedioSPI {
void call();
}Implementation 1
public class Mp3Vedio implements VedioSPI {
@Override
public void call() {
System.out.println("this is mp3 call");
}
}Implementation 2
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.
Related Test
public class VedioSPITest {
public static void main(String[] args) {
ServiceLoader
serviceLoader = ServiceLoader.load(VedioSPI.class);
serviceLoader.forEach(t -> t.call());
}
}The test shows that ServiceLoader locates and invokes each implementation.
Source Code Analysis
ServiceLoader implements Iterable ; its iterator() creates a LazyIterator that lazily loads provider classes. All provider configuration files reside in META-INF/services/ , which is immutable.
Drawbacks of Java SPI include:
Providers are discovered only via iteration.
Configuration files must be placed in META-INF/services/ .
Spring SPI
Spring adopts the same idea but uses spring.factories to map interfaces to multiple implementations in a single file, improving convenience.
Spring Example
Define Interface
public interface DataBaseSPI {
void getConnection();
}Implementations
// DB2 implementation
public class DB2DataBase implements DataBaseSPI {
@Override
public void getConnection() {
System.out.println("this database is db2");
}
}
// MySQL implementation
public class MysqlDataBase implements DataBaseSPI {
@Override
public void getConnection() {
System.out.println("this is mysql database");
}
}Add a spring.factories file under META-INF/ with the entry:
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 spi : dataBaseSPIs) {
spi.getConnection();
}
}
}The output demonstrates that Spring loads both implementations from a single spring.factories file.
Comparison
Java SPI: one configuration file per interface, all files under META-INF/services/ .
Spring SPI: a single spring.factories file mapping multiple interfaces to their implementations, separated by commas.
Spring SPI Source Analysis
The loadFactoryNames method parses spring.factories to retrieve fully‑qualified implementation class names, then uses reflection to instantiate them.
Overall, Spring's approach retains the SPI concept while simplifying configuration and reducing the number of files required.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
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.