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.
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.MyJsonPropertySourceLoaderThe 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.
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 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.
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.
