Fundamentals 14 min read

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.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Unlocking Java’s SPI: How JDK, Dubbo, and Spring Extend Your 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.XMLConfiguration

Loading 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.Bumblebee

Interfaces 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.YamlPropertySourceLoader

Spring’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.

SPI comparison diagram
SPI comparison diagram

References

Introduction to the Service Provider Interfaces - Oracle

Dubbo SPI - Apache Dubbo

Creating Your Own Auto-configuration - Spring

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaDubbospringJDKSPIextensibilityservice-provider-interface
Java Backend Technology
Written by

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!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.