Unlocking Java’s SPI: How JDK, Dubbo, and Spring Extend Your Applications
This article explains the Service Provider Interface (SPI) concept, demonstrates its use in plain JDK, Dubbo, and Spring frameworks with code examples, compares their strengths and limitations, and shows how SPI enhances extensibility and modularity in Java applications.
SPI (Service Provider Interface) is a service discovery mechanism that registers the fully qualified names of implementation classes in configuration files, allowing the runtime to dynamically replace implementations.
The article focuses on SPI features and usage without delving into source code analysis.
What is SPI used for?
Consider a new logging framework super-logger that uses an XML configuration file and defines a configuration interface:
package com.github.kongwu.spisamples;
public interface SuperLoggerConfiguration {
void configure(String configFile);
}A default XML implementation might look like:
package com.github.kongwu.spisamples;
public class XMLConfiguration implements SuperLoggerConfiguration {
public void configure(String configFile) {
// ...
}
}Initially the factory directly instantiates this class, which limits extensibility because adding a new format (e.g., YAML) would require modifying LoggerFactory. Using SPI solves this problem.
JDK SPI
JDK provides SPI via java.util.ServiceLoader. Implementations are listed in files under META-INF/services/ named after the interface.
META-INF/services/com.github.kongwu.spisamples.SuperLoggerConfiguration:
com.github.kongwu.spisamples.XMLConfigurationLoading implementations:
ServiceLoader<SuperLoggerConfiguration> serviceLoader = ServiceLoader.load(SuperLoggerConfiguration.class);
Iterator<SuperLoggerConfiguration> iterator = serviceLoader.iterator();
SuperLoggerConfiguration configuration;
while (iterator.hasNext()) {
configuration = iterator.next(); // load and initialize
}
configuration.configure(configFile);The factory is rewritten to use this loader:
package com.github.kongwu.spisamples;
public class LoggerFactory {
static {
ServiceLoader<SuperLoggerConfiguration> serviceLoader = ServiceLoader.load(SuperLoggerConfiguration.class);
Iterator<SuperLoggerConfiguration> iterator = serviceLoader.iterator();
SuperLoggerConfiguration configuration;
while (iterator.hasNext()) {
configuration = iterator.next();
}
configuration.configure(configFile);
}
public static getLogger(Class clazz) {
// ...
}
}Using an iterator rather than a single get method ensures that all implementations are considered, but the actual instance chosen depends on class‑path order, which can be unpredictable.
Dubbo SPI
Dubbo enhances SPI with named extensions. Configuration files reside in META-INF/dubbo and use key‑value pairs:
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.BumblebeeInterfaces are annotated with @SPI and implementations are retrieved by name:
@SPI
public interface Robot {
void sayHello();
}
public class OptimusPrime implements Robot {
@Override
public void sayHello() { System.out.println("Hello, I am Optimus Prime."); }
}
public class Bumblebee implements Robot {
@Override
public void sayHello() { System.out.println("Hello, I am Bumblebee."); }
}
public class DubboSPITest {
@Test
public void sayHello() throws Exception {
ExtensionLoader<Robot> loader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = loader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = loader.getExtension("bumblebee");
bumblebee.sayHello();
}
}The key advantage is the ability to select a specific implementation via its alias, and a default implementation can be specified with @SPI("defaultName").
Spring SPI
Spring uses a single file META-INF/spring.factories to list implementations for various interfaces. For example, loading all LoggingSystemFactory implementations:
List<LoggingSystemFactory> factories = SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class, classLoader);Typical spring.factories content:
# Logging Systems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoaderSpring’s SPI behaves similarly to JDK’s but consolidates all extensions into one file; class‑path order determines loading precedence, with project‑level files loaded before dependency files.
Comparison
JDK SPI is the simplest and has no external dependencies, but it lacks naming and ordering control. Dubbo SPI offers rich features such as named extensions, default implementations, and priority loading, though it is tied to the Dubbo ecosystem. Spring SPI provides a modest improvement over JDK by aggregating extensions in a single file and integrating with IDE support, yet its capabilities are comparable to JDK’s.
References
Introduction to the Service Provider Interfaces - Oracle
Dubbo SPI - Apache Dubbo
Creating Your Own Auto-configuration - Spring
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.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
