Java Plugin Development: SPI, Spring Factories, and Custom Extension Mechanisms
This article explains the concept of plugin‑based development in Java, outlines its advantages such as decoupling and extensibility, and provides detailed implementation guides using ServiceLoader, custom configuration files, and Spring Boot's spring.factories mechanism with complete code examples.
Plugin‑oriented development is widely used in many programming languages and frameworks (e.g., Jenkins, Rancher, IDEs) to improve system extensibility, modularity, and third‑party integration. The article first discusses why plugins are beneficial, highlighting module decoupling, enhanced extensibility, and easy third‑party access.
For Java, several common plugin implementation ideas are presented:
SPI mechanism (Service Provider Interface)
Convention‑based configuration with reflection
Spring Boot's spring.factories extension point
Dynamic loading of external JARs via custom class loaders
1. ServiceLoader (SPI) example
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";
}
}
// resources/META-INF/services/com.example.MessagePlugin
com.example.AliyunMsg
com.example.TencentMsg
public class Main {
public static void main(String[] args) {
ServiceLoader
loader = ServiceLoader.load(MessagePlugin.class);
for (MessagePlugin plugin : loader) {
plugin.sendMsg(new HashMap<>());
}
}
}This code demonstrates loading multiple implementations at runtime and invoking them without compile‑time dependencies.
2. Custom configuration‑driven approach
# application.yml (or custom yaml)
server:
port: 8081
impl:
name: com.congge.plugins.spi.MessagePlugin
clazz:
- com.congge.plugins.impl.TencentMsg
- com.congge.plugins.impl.AliyunMsg
// Configuration POJO
@Getter @Setter @ConfigurationProperties("impl")
public class ClassImpl {
private String name;
private String[] clazz;
}
@RestController
public class SendMsgController {
@Autowired
private ClassImpl classImpl;
@GetMapping("/sendMsg")
public String sendMsg() throws Exception {
for (String className : classImpl.getClazz()) {
Class
cls = Class.forName(className);
MessagePlugin plugin = (MessagePlugin) cls.getDeclaredConstructor().newInstance();
plugin.sendMsg(new HashMap<>());
}
return "success";
}
}This method reads a custom YAML file to determine which plugin classes to load, offering runtime flexibility.
3. Loading external JARs dynamically
public class ServiceLoaderUtils {
public static void loadJarsFromAppFolder() throws Exception {
String path = "E:/code-self/bitzpp/lib";
File dir = new File(path);
for (File file : dir.listFiles()) {
if (file.isFile()) loadJarFile(file);
}
}
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);
}
}
// Example usage
String result = ServiceLoaderUtils.invokeMethod("hello");The utility loads JAR files from a specified directory and uses reflection to invoke methods inside the loaded classes.
4. Spring Boot SPI via spring.factories
// resources/META-INF/spring.factories
com.congge.plugin.spi.SmsPlugin=\
com.congge.plugin.impl.SystemSmsImpl,\
com.congge.plugin.impl.BizSmsImpl
public interface SmsPlugin {
void sendMessage(String message);
}
public class BizSmsImpl implements SmsPlugin {
@Override
public void sendMessage(String message) {
System.out.println("BizSmsImpl: " + message);
}
}
public class SystemSmsImpl implements SmsPlugin {
@Override
public void sendMessage(String message) {
System.out.println("SystemSmsImpl: " + message);
}
}
@RestController
public class SmsController {
@GetMapping("/sendMsgV3")
public String sendMsgV3(String msg) {
List
plugins = SpringFactoriesLoader.loadFactories(SmsPlugin.class, null);
for (SmsPlugin p : plugins) {
p.sendMessage(msg);
}
return "success";
}
}SpringBoot automatically discovers implementations listed in spring.factories , allowing seamless plugin integration.
5. Full case study
The article walks through a realistic scenario where three micro‑services share a common plugin interface for SMS sending. It shows how to define the interface, package implementations as separate JARs, configure them via SPI or Spring factories, and select a concrete plugin at runtime based on configuration parameters.
Finally, the author emphasizes that plugin mechanisms are pervasive across languages and frameworks, and mastering them is valuable for both development and architectural design.
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
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.