Comprehensive Guide to SpringBoot Plugin Development

This article provides a detailed walkthrough of SpringBoot plugin development, covering the benefits of modular decoupling, common implementation approaches such as Java SPI, custom configuration, Spring factories, and practical code examples that demonstrate how to create, load, and use plugins for extensible SMS services.

IoT Full-Stack Technology
IoT Full-Stack Technology
IoT Full-Stack Technology
Comprehensive Guide to SpringBoot Plugin Development

1. Introduction

Plugins improve module decoupling, extensibility, and openness. A plugin mechanism lets a service switch between SMS providers (e.g., Aliyun, Tencent) without changing core business logic.

1.1 Benefits of Using Plugins

Higher degree of decoupling between service modules.

Improved extensibility and openness of the system.

Easy third‑party integration with minimal intrusion, supporting hot‑loading via configuration.

2. Common Implementation Approaches in Java

Typical techniques for plugin development in Java:

SPI mechanism.

Convention‑based configuration with reflection.

Spring Boot Factories mechanism.

Spring plugin‑core library.

Java Agent (instrumentation).

Spring built‑in extension points.

Spring AOP.

2.1 ServiceLoader (Java SPI)

ServiceLoader

is the JDK implementation of the SPI pattern. It loads implementations of an interface defined in META-INF/services files.

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

Two example 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";
    }
}

Loading the implementations:

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

2.2 Custom Configuration and Reflection

When many plugins exist, managing META-INF/services files can become cumbersome. An alternative is to define a configuration file that lists implementation class names and load them via reflection.

# resources/application.yml
impl:
  clazz:
    - com.congge.plugins.impl.TencentMsg
    - com.congge.plugins.impl.AliyunMsg

Utility class to load JARs from a directory and instantiate the configured classes:

@Component
public class ServiceLoaderUtils {
    @Autowired
    ClassImpl classImpl;

    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 method = URLClassLoader.class.getMethod("addURL", URL.class);
        method.invoke(cl, url);
    }

    public String doExecuteMethod() throws Exception {
        for (String className : classImpl.getClazz()) {
            Class<?> clazz = Class.forName(className);
            MessagePlugin plugin = (MessagePlugin) clazz.getDeclaredConstructor().newInstance();
            plugin.sendMsg(new HashMap<>());
        }
        return "success";
    }
}

2.3 Spring Boot SPI Mechanism

Spring Boot provides its own SPI based on META-INF/spring.factories. SpringFactoriesLoader scans all JARs on the classpath and returns instances of the configured types.

public class SpringFactoriesLoader {
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        // reads META-INF/spring.factories and returns class names
    }
    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
        // instantiates the classes
    }
}

Example interface and two implementations:

public interface SmsPlugin {
    void sendMessage(String message);
}

public class BizSmsImpl implements SmsPlugin {
    @Override
    public void sendMessage(String message) {
        System.out.println("this is BizSmsImpl sendMessage..." + message);
    }
}

public class SystemSmsImpl implements SmsPlugin {
    @Override
    public void sendMessage(String message) {
        System.out.println("this is SystemSmsImpl sendMessage..." + message);
    }
}

Corresponding spring.factories file (under src/main/resources/META-INF):

com.example.SmsPlugin=\
com.example.impl.SystemSmsImpl,\
com.example.impl.BizSmsImpl

Loading the plugins in a controller:

@RestController
public class SendMsgController {
    @GetMapping("/sendMsgV3")
    public String sendMsgV3(@RequestParam String msg) {
        List<SmsPlugin> plugins = SpringFactoriesLoader.loadFactories(SmsPlugin.class, null);
        for (SmsPlugin p : plugins) {
            p.sendMessage(msg);
        }
        return "success";
    }
}

3. SpringBoot Plugin Implementation Summary

Spring Boot’s spring.factories mechanism is the most suitable extension point for plugin development. It allows each module to provide its own implementation without affecting others, and the core application can discover all implementations at runtime.

4. Practical Case Study

The case demonstrates a real‑world scenario with three micro‑services. Service biz‑pp defines the MessagePlugin interface. Two implementations ( bitpt for Aliyun and miz‑pt for Tencent) are packaged as separate JARs and registered via SPI or Spring factories. The main service loads the appropriate implementation based on a configuration property ${msg.type}. If no plugin is found, a default SMS service is used.

4.1 Core Code in biz‑pp

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

Factory utility to select the target plugin:

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;
    }
}

Service that uses the selected plugin:

@Service
public class SmsService {
    @Value("${msg.type}")
    private String msgType;

    public String sendMsg(String msg) {
        MessagePlugin plugin = PluginFactory.getTargetPlugin(msgType);
        Map<String,Object> params = new HashMap<>();
        if (plugin != null) {
            return plugin.sendMsg(params);
        }
        return defaultSmsService.sendMsg(params);
    }
}

4.2 Demonstration

After building and deploying the application, calling http://localhost:8087/sendMsg?msg=hello triggers the selected SMS implementation, and the console shows the corresponding log output, confirming that the plugin mechanism works as intended.

5. Conclusion

The plugin architecture is widely adopted across programming languages, frameworks, and middleware. Understanding and mastering these mechanisms—Java SPI, Spring factories, and custom reflection‑based loading—provides a solid foundation for building extensible, maintainable systems.

SpringBootPlugin DevelopmentServiceLoaderJava SPISMSSpring Factories
IoT Full-Stack Technology
Written by

IoT Full-Stack Technology

Dedicated to sharing IoT cloud services, embedded systems, and mobile client technology, with no spam ads.

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.