Plug‑and‑Play Spring Boot: Building a Plugin Architecture

This article explains how to implement a plug‑and‑play development model in Spring Boot using Java SPI, custom configuration, and Spring Factories, providing step‑by‑step code examples, a multi‑module microservice case study, and practical guidance for creating extensible, decoupled applications.

Programmer XiaoFu
Programmer XiaoFu
Programmer XiaoFu
Plug‑and‑Play Spring Boot: Building a Plugin Architecture

Introduction

The plug‑in development model is widely used in many languages and frameworks (e.g., Jenkins, Rancher, IDEs) to improve system extensibility and flexibility. In Java, plugins enable higher‑level module decoupling, easier third‑party integration, and hot‑loading of functionality.

Benefits of Plugins

Module decoupling : Plugins allow dynamic replacement of implementations, such as switching SMS providers at runtime.

Enhanced extensibility : Spring’s rich ecosystem is built on numerous extension points that can be leveraged via plugins.

Third‑party integration : External systems can implement a predefined plug‑in interface with minimal intrusion, even supporting configuration‑driven hot loading.

Common Java Plugin Implementations

Typical approaches include:

SPI mechanism (ServiceLoader)

Custom configuration files combined with reflection

Spring Boot Factories mechanism

Java agent (instrumentation)

Spring built‑in extension points

Third‑party plugin libraries (e.g., spring‑plugin‑core)

Spring AOP

1. ServiceLoader (SPI) Approach

Java’s Service Provider Interface (SPI) enables runtime discovery of implementations. The article defines a MessagePlugin interface and two implementations ( AliyunMsg and TencentMsg).

public interface MessagePlugin {
    String sendMsg(Map msgMap);
}
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";
    }
}

A resource file under META-INF/services/com.congge.plugins.spi.MessagePlugin lists the fully‑qualified class names. Loading code:

public static void main(String[] args) {
    ServiceLoader<MessagePlugin> loader = ServiceLoader.load(MessagePlugin.class);
    for (MessagePlugin plugin : loader) {
        plugin.sendMsg(new HashMap());
    }
}

Running this prints the messages from both implementations, demonstrating dynamic discovery.

SPI diagram
SPI diagram

2. Custom Configuration + Reflection

To avoid proliferating META-INF files, a custom configuration file can specify implementation class names. The article shows a YAML snippet:

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

A POJO ( ClassImpl) holds the configuration, and a controller loads the classes via Class.forName and invokes sendMsg on each instance.

@RestController
public class SendMsgController {
    @Autowired
    ClassImpl classImpl;

    @GetMapping("/sendMsg")
    public String sendMsg() throws Exception {
        for (String clazzName : classImpl.getClazz()) {
            Class pluginClass = Class.forName(clazzName);
            MessagePlugin plugin = (MessagePlugin) pluginClass.newInstance();
            plugin.sendMsg(new HashMap());
        }
        return "success";
    }
}
Custom config loading
Custom config loading

3. Loading JARs from a Directory

When implementations are packaged as separate JARs, the article provides a utility ( ServiceLoaderUtils) that scans a configured directory, loads each JAR with URLClassLoader, and invokes a known method via reflection.

public static void loadJarsFromAppFolder() throws Exception {
    String path = "E:\\code-self\\bitzpp\\lib";
    File dir = new File(path);
    if (dir.isDirectory()) {
        for (File f : dir.listFiles()) {
            if (f.isFile()) loadJarFile(f);
        }
    } else {
        loadJarFile(dir);
    }
}

private static void loadJarFile(File file) throws Exception {
    URL url = file.toURI().toURL();
    URLClassLoader cl = (URLClassLoader) ClassLoader.getSystemClassLoader();
    Method m = URLClassLoader.class.getMethod("sendMsg", Map.class);
    m.setAccessible(true);
    m.invoke(cl, url);
}
Jar loading utility
Jar loading utility

4. Spring Boot spring.factories Mechanism

Spring Boot extends the SPI idea with spring.factories. The class SpringFactoriesLoader reads META-INF/spring.factories from all JARs on the classpath and returns instances of the configured types.

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    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 props = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
        String names = props.getProperty(factoryClassName);
        result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(names)));
    }
    return result;
}

Example: an SmsPlugin interface with two implementations ( BizSmsImpl and SystemSmsImpl) is declared in spring.factories:

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

A controller loads all implementations via SpringFactoriesLoader.loadFactories and invokes sendMessage on each.

@GetMapping("/sendMsgV3")
public String sendMsgV3(String msg) throws Exception {
    List<SmsPlugin> plugins = SpringFactoriesLoader.loadFactories(SmsPlugin.class, null);
    for (SmsPlugin p : plugins) {
        p.sendMessage(msg);
    }
    return "success";
}
Spring Factories usage
Spring Factories usage

5. End‑to‑End Case Study

The article assembles a realistic microservice scenario:

biz‑pp : defines MessagePlugin and is packaged as a JAR.

bitpt and miz‑pt : depend on biz‑pp and provide Alibaba and Tencent SMS implementations respectively.

biz‑pp includes a PluginFactory that selects a plugin based on a configuration property ( msg.type).

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 SmsService autowires the selected plugin (or falls back to a default implementation) and a REST controller exposes /sendMsg. Maven dependencies include the two implementation JARs, the core biz‑pp JAR, and Lombok.

<dependency>
    <groupId>com.congge</groupId>
    <artifactId>biz-pt</artifactId>
    <version>1.0‑SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.congge</groupId>
    <artifactId>miz-pt</artifactId>
    <version>1.0‑SNAPSHOT</version>
</dependency>

After starting the application, calling http://localhost:8087/sendMsg?msg=hello triggers the selected plugin, prints the SMS sending log, and returns success.

Endpoint output
Endpoint output

Conclusion

By leveraging Java SPI, custom configuration, and Spring Boot’s spring.factories, developers can build a plug‑and‑play architecture that cleanly separates core logic from optional extensions, supports hot‑loading, and simplifies third‑party integration in backend systems.

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.

microservicesPlugin ArchitectureSpring BootJava SPISpring Factories
Programmer XiaoFu
Written by

Programmer XiaoFu

xiaofucode.com – a programmer learning guide driven by the pursuit of profit

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.