Backend Development 21 min read

Java Plugin Development: SPI, ServiceLoader, and Spring Factories in Spring Boot

This article explains the concept of plugin-based development, outlines its benefits such as module decoupling and extensibility, and provides detailed Java implementations using ServiceLoader, custom configuration files, dynamic JAR loading, and Spring Boot's spring.factories mechanism with complete code examples.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Java Plugin Development: SPI, ServiceLoader, and Spring Factories in Spring Boot

1. Introduction

Plugin-based development is widely used in many programming languages and frameworks, such as Jenkins, Rancher, IDEs like IDEA and VSCode, to improve system extensibility and flexibility.

1.1 Benefits of Using Plugins

1.1.1 Module Decoupling

Plugins achieve a higher degree of decoupling compared to traditional design patterns, allowing dynamic replacement of implementations, e.g., switching SMS providers at runtime.

1.1.2 Improved Extensibility and Openness

Frameworks like Spring provide built‑in extension points that make it easy to integrate additional middleware through plugins.

1.1.3 Easy Third‑Party Integration

Third‑party systems can implement custom plugins with minimal intrusion, supporting hot‑loading and configuration‑driven activation.

1.2 Common Implementation Ideas

Typical Java approaches include SPI mechanism, configuration‑driven reflection, Spring Boot factories, and dynamic JAR loading.

2. Java Common Plugin Implementations

2.1 ServiceLoader (SPI) Approach

ServiceLoader implements the JDK SPI pattern. Define an interface and provide implementations listed in META-INF/services/<interface‑full‑name> . Example:

public interface MessagePlugin {
    String sendMsg(Map msgMap);
}

Two implementations:

public class AliyunMsg implements MessagePlugin {
    @Override
    public String sendMsg(Map msgMap) {
        System.out.println("aliyun sendMsg");
        return "aliyun sendMsg";
    }
}

public class TencentMsg implements MessagePlugin {
    @Override
    public String sendMsg(Map msgMap) {
        System.out.println("tencent sendMsg");
        return "tencent sendMsg";
    }
}

Place the fully qualified class names in resources/META-INF/services/com.congge.plugins.spi.MessagePlugin and load them:

ServiceLoader<MessagePlugin> loader = ServiceLoader.load(MessagePlugin.class);
for (MessagePlugin plugin : loader) {
    plugin.sendMsg(new HashMap());
}

2.2 Custom Configuration & Reflection

Define a YAML/Properties file that lists implementation classes, read it, and instantiate classes via reflection. Example configuration:

impl:
  name: com.congge.plugins.spi.MessagePlugin
  clazz:
    - com.congge.plugins.impl.TencentMsg
    - com.congge.plugins.impl.AliyunMsg

Load classes:

for (String className : classImpl.getClazz()) {
    Class
cls = Class.forName(className);
    MessagePlugin plugin = (MessagePlugin) cls.getDeclaredConstructor().newInstance();
    plugin.sendMsg(new HashMap());
}

2.3 Dynamic JAR Loading

Read JAR files from a designated directory, create a URLClassLoader, and invoke the plugin method via reflection. Sample utility:

public static void loadJarFile(File jar) throws Exception {
    URL url = jar.toURI().toURL();
    URLClassLoader cl = (URLClassLoader) ClassLoader.getSystemClassLoader();
    Method method = URLClassLoader.class.getMethod("addURL", URL.class);
    method.invoke(cl, url);
}

After loading, instantiate classes listed in the configuration and call sendMsg .

3. Spring Boot Plugin Implementation

3.1 Spring Boot SPI (spring.factories)

Spring Boot uses META-INF/spring.factories to map interfaces to implementation classes. Example entry:

com.congge.plugin.spi.SmsPlugin=\
com.congge.plugin.impl.SystemSmsImpl,\
com.congge.plugin.impl.BizSmsImpl

Load implementations with SpringFactoriesLoader.loadFactories :

List<SmsPlugin> plugins = SpringFactoriesLoader.loadFactories(SmsPlugin.class, null);
for (SmsPlugin p : plugins) {
    p.sendMessage("hello");
}

3.2 Example Service and Controller

@RestController
public class SendMsgController {
    @GetMapping("/sendMsg")
    public String sendMsg() throws Exception {
        // invoke loaded plugins
        return "success";
    }
}

4. Case Study: End‑to‑End Plugin System

The article walks through a realistic scenario with three micro‑services (biz‑pp, bitpt, miz‑pt). Biz‑pp defines the MessagePlugin interface, bitpt and miz‑pt provide Alibaba Cloud and Tencent implementations, and the main service loads the appropriate plugin based on configuration.

4.1 Key Code Highlights

public class PluginFactory {
    public static MessagePlugin getTargetPlugin(String type) {
        ServiceLoader<MessagePlugin> loader = ServiceLoader.load(MessagePlugin.class);
        for (MessagePlugin p : loader) {
            if (type.equals("aliyun") && p instanceof BitptImpl) return p;
            if (type.equals("tencent") && p instanceof MizptImpl) return p;
        }
        return null;
    }
}

The controller uses this factory to send messages, falling back to a default implementation when no plugin is found.

5. Conclusion

Plugin mechanisms are pervasive across languages, frameworks, and middleware. Mastering SPI, ServiceLoader, custom configuration, and Spring Boot’s factories equips developers with powerful extensibility tools for modern backend systems.

JavaModularizationPluginSpring BootSPIServiceLoaderspring.factories
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
Reader feedback

How this landed with the community

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