Mastering Java, Spring, and Dubbo SPI: Principles, Differences, and Real-World Examples
This article explains the principles and differences of the SPI mechanisms in Java, Spring, and Dubbo, covering ServiceLoader, SpringFactoriesLoader, and Dubbo's ExtensionLoader, with detailed demos, core implementation details, advantages, drawbacks, and typical usage scenarios for each framework.
Hello everyone, I am Su San. Today we will discuss the principles and differences of the SPI mechanisms in Java, Spring, and Dubbo.
What is SPI
SPI (Service Provider Interface) is a dynamic discovery and replacement mechanism that decouples interfaces from implementations, allowing third‑party providers to supply implementations via configuration files, thereby enhancing framework extensibility.
Java SPI Mechanism – ServiceLoader
ServiceLoader is Java's simple SPI implementation. It follows two conventions:
The configuration file must be placed under META-INF/services/.
The file name must be the fully‑qualified name of the interface, and its content must list the fully‑qualified names of the implementations.
Demo
Step 1: Define an interface and its implementation.
public interface LoadBalance {
}
public class RandomLoadBalance implements LoadBalance {
}Step 2: Create a file named with the interface's fully‑qualified name under META-INF/services/ containing the implementation's fully‑qualified name.
Test class:
public class ServiceLoaderDemo {
public static void main(String[] args) {
ServiceLoader<LoadBalance> loader = ServiceLoader.load(LoadBalance.class);
Iterator<LoadBalance> iterator = loader.iterator();
while (iterator.hasNext()) {
LoadBalance lb = iterator.next();
System.out.println("Loaded LoadBalance implementation: " + lb);
}
}
}Result:
The framework internally uses similar code; users only need to provide the interface, its implementation, and the SPI file.
Implementation Details
Core code of ServiceLoader reads the file META-INF/services/InterfaceFullName via a ClassLoader, parses it, and uses reflection to instantiate the class.
Thus ServiceLoader simply reads the configuration file and creates instances via reflection.
Advantages and Disadvantages
Resource waste: all implementations are instantiated even if not needed.
Cannot directly select a specific implementation; selection must be coded based on interface design.
Use Cases
When every loaded implementation must be used.
When a specific implementation can be chosen through interface design.
Spring SPI Mechanism – SpringFactoriesLoader
Spring provides its own SPI implementation via SpringFactoriesLoader.
Spring's conventions:
The configuration file must be META-INF/spring.factories.
The file contains key‑value pairs; a key may have multiple comma‑separated values, all being fully‑qualified class names.
Demo
Create META-INF/spring.factories with the entry LoadBalance=RandomLoadBalance.
Test class:
public class SpringFactoriesLoaderDemo {
public static void main(String[] args) {
List<LoadBalance> lbs = SpringFactoriesLoader.loadFactories(LoadBalance.class, MyEnableAutoConfiguration.class.getClassLoader());
for (LoadBalance lb : lbs) {
System.out.println("Loaded LoadBalance object: " + lb);
}
}
}Result:
Core Principle
SpringFactoriesLoader reads META-INF/spring.factories, parses the key‑value pairs, and loads the classes similarly to Java's ServiceLoader.
Use Cases
Spring heavily uses this mechanism, especially in Spring Boot. Examples:
1. Auto‑configuration
Before Spring Boot 3.0, auto‑configuration classes were loaded via SpringFactoriesLoader. After 3.0, they are loaded from
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
2. PropertySourceLoader
Spring Boot uses SPI to load PropertiesPropertySourceLoader and YamlPropertySourceLoader for .properties and .yaml files.
Comparison with Java SPI
Spring uses a single spring.factories file instead of one per interface.
Spring's key‑value pairs are more flexible; keys need not be related to values.
Spring provides loadFactoryNames to obtain class names, which Java SPI lacks.
Dubbo SPI Mechanism – ExtensionLoader
Dubbo implements SPI via ExtensionLoader. Each interface has its own ExtensionLoader instance.
Dubbo's conventions:
The interface must be annotated with @SPI.
Configuration files can reside in META-INF/services/, META-INF/dubbo/internal/, META-INF/dubbo/, or META-INF/dubbo/external/, with the file name being the interface's fully‑qualified name.
File content consists of key‑value pairs where the key is a short name and the value is the implementation's fully‑qualified name.
Demo
Annotate the interface:
@SPI
public interface LoadBalance {
}Use a key‑value entry in META-INF/services/ such as random=com.sanyou.spi.demo.RandomLoadBalance.
random=com.sanyou.spi.demo.RandomLoadBalanceTest class:
public class ExtensionLoaderDemo {
public static void main(String[] args) {
ExtensionLoader<LoadBalance> loader = ExtensionLoader.getExtensionLoader(LoadBalance.class);
LoadBalance lb = loader.getExtension("random");
System.out.println("Got implementation for 'random': " + lb);
}
}Result shows that Dubbo can retrieve a specific implementation by its short name.
Dubbo Core Mechanisms
1. Adaptive Mechanism
Dubbo ensures each interface has at most one adaptive class, obtained via getAdaptiveExtension(). The adaptive class selects the concrete implementation at runtime based on parameters.
Adaptive classes can be declared explicitly with @Adaptive or generated automatically by Dubbo.
@Adaptive
public class RandomLoadBalance implements LoadBalance {
}2. IOC and AOP
2.1 Dependency Injection
Dubbo performs setter‑based injection; the injected object is usually the adaptive instance, and in a Spring environment it can be a Spring bean.
public class RoundRobinLoadBalance implements LoadBalance {
private LoadBalance loadBalance;
public void setLoadBalance(LoadBalance loadBalance) {
this.loadBalance = loadBalance;
}
}2.2 Interface Callbacks
Implementing lifecycle interfaces (e.g., Lifecycle) allows Dubbo to invoke callbacks during creation and destruction.
2.3 Automatic Wrapping
Dubbo treats classes with a single‑argument constructor (the argument type being the interface) as Wrapper classes, enabling static‑proxy‑style AOP.
public class RoundRobinLoadBalance implements LoadBalance {
private final LoadBalance delegate;
public RoundRobinLoadBalance(LoadBalance delegate) {
this.delegate = delegate;
}
}When requesting the random implementation, Dubbo may wrap it with a Wrapper class, forming a responsibility chain.
3. Automatic Activation
Classes annotated with @Activate are automatically selected based on invocation parameters via getActivateExtension(). This is heavily used in Dubbo's filter chain to choose filters for provider or consumer sides.
@Activate
public interface RandomLoadBalance {}Conclusion
The core of all three SPI mechanisms is reading a configuration file via I/O, parsing the content, and using reflection to instantiate the target class. Java's SPI is simple and limited; Spring builds on Java's SPI with minor simplifications; Dubbo extends the concept with adaptive, wrapper, and automatic‑activation features to meet its framework requirements.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.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.
