Understanding Spring SPI: Concepts, Implementation, and Practical Applications

This article explains Spring's Service Provider Interface (SPI) mechanisms—including spring.handlers and spring.factories—covers their implementation details with code examples, demonstrates real‑world usage in MyBatis, Dubbo and Spring Boot, and shows how to create a custom configuration file loader.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Understanding Spring SPI: Concepts, Implementation, and Practical Applications

1. Concept

SPI (Service Provider Interface) is a mechanism for decoupling interfaces from their implementations, enabling plug‑in style extensions. In Java, the standard SPI is realized by ServiceLoader. Spring introduced its own SPI implementations: spring.handlers (XML‑based) and later spring.factories (similar to JDK SPI).

2. Implementation

2.1 spring.handlers SPI

Spring defines the NamespaceHandlerResolver interface with a default implementation DefaultNamespaceHandlerResolver. This resolver loads META-INF/spring.handlers files from the classpath and instantiates the mapped NamespaceHandler classes.

// Load all META-INF/spring.handlers files
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);

// Instantiate the handler class for each namespace URI
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);

The resolver is used by BeanDefinitionParserDelegate during custom element parsing and decoration.

2.2 spring.factories SPI

Spring Boot’s spring.factories file follows a key=value1,value2,… format. The SpringFactoriesLoader scans all JARs for this file, loads the listed classes, and returns them as a list.

// Load factories for a given interface (similar to ServiceLoader)
List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(BeanInfoFactory.class, classLoader);
// Scan all META-INF/spring.factories files
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<>();
while (urls.hasMoreElements()) {
    URL url = urls.nextElement();
    Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
    String propertyValue = properties.getProperty(factoryClassName);
    for (String factoryName : StringUtils.commaDelimitedListToStringArray(propertyValue)) {
        result.add(factoryName.trim());
    }
}
return result;

Spring Boot heavily utilizes this mechanism (e.g., in SpringApplication) to provide extensibility points.

3. Application

Early integrations such as MyBatis and Dubbo relied on spring.handlers to register their beans. In the Boot era, spring.factories is used throughout container startup, environment preparation, and context loading, enabling features like dynamic property decryption or custom annotations.

4. Practice – Loading a Custom .xyz Configuration File

To add a new configuration format, a custom PropertySourceLoader is registered via spring.factories:

# This is the entry in spring.factories
org.springframework.boot.env.PropertySourceLoader=top.hiccup.json.MyJsonPropertySourceLoader

The loader implementation declares support for the xyz extension and parses the file into a MapPropertySource:

public class MyJsonPropertySourceLoader implements PropertySourceLoader {
    @Override
    public String[] getFileExtensions() {
        return new String[]{"xyz"};
    }
    @Override
    public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        // Simple JSON parsing (no nested structures handled)
        JSONObject json = JSONObject.parseObject(sb.toString());
        List<PropertySource<?>> propertySources = new ArrayList<>();
        MapPropertySource mapPropertySource = new MapPropertySource(resource.getFilename(), json);
        propertySources.add(mapPropertySource);
        return propertySources;
    }
}

Using the loader:

ConfigurableApplicationContext ctx = SpringApplication.run(BootTest.class, args);
Custom custom = ctx.getBean(Custom.class);
System.out.println(custom.name);
System.out.println(custom.age);

The demonstration (source code available on GitHub) shows that the custom .xyz file is loaded and its properties are injected without modifying Spring’s core code, illustrating the power of Spring SPI.

In summary, Spring SPI—through spring.handlers and spring.factories —provides a flexible plug‑in architecture that enables developers to extend the framework safely and efficiently.

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.

JavaBackend Developmentplugin architectureSpring BootSPI
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

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.