Mastering Java Plugin Architecture: From SPI to Spring Factories
This article explains how to implement plugin mechanisms in Java using SPI and Spring Boot's spring.factories, covering benefits, common approaches, detailed code examples, custom configuration loading, and a complete real‑world case study to guide developers in building extensible applications.
1. Introduction
Plugins enable module decoupling, improve extensibility, and simplify third‑party integration. In Java, several mechanisms such as the standard Service Provider Interface (SPI) and Spring Boot's spring.factories can be used to achieve this.
2. Common Plugin Implementation Ideas
2.1 Benefits of Plugins
Module decoupling
Improved extensibility and openness
Easy third‑party integration
2.2 Typical Java Implementations
SPI (ServiceLoader)
Custom configuration with reflection
Loading external JARs at runtime
3. Java SPI Implementation
Define a service interface and provide concrete implementations. The interface is placed in a shared JAR, while each implementation is packaged in its own JAR with a service descriptor.
public interface MessagePlugin {
String sendMsg(Map<String, Object> msgMap);
}Two example implementations:
public class AliyunMsg implements MessagePlugin {
@Override
public String sendMsg(Map<String, Object> msgMap) {
System.out.println("aliyun sendMsg");
return "aliyun sendMsg";
}
}
public class TencentMsg implements MessagePlugin {
@Override
public String sendMsg(Map<String, Object> msgMap) {
System.out.println("tencent sendMsg");
return "tencent sendMsg";
}
}In META-INF/services/com.example.MessagePlugin list the implementation class names:
com.example.AliyunMsg
com.example.TencentMsgLoad and invoke the plugins with ServiceLoader:
ServiceLoader<MessagePlugin> loader = ServiceLoader.load(MessagePlugin.class);
for (MessagePlugin plugin : loader) {
plugin.sendMsg(new HashMap<>());
}4. Custom Configuration Loading
Sometimes the implementation JARs are not known at compile time. A custom configuration file can list the fully qualified class names, which are then loaded via reflection or a URLClassLoader.
public class ClassImpl {
private String name;
private String[] clazz;
// getters and setters
}Example YAML/Properties:
server:
port: 8081
impl:
name: com.example.MessagePlugin
clazz:
- com.example.AliyunMsg
- com.example.TencentMsgLoading logic (simplified):
for (String className : classImpl.getClazz()) {
Class<?> cls = Class.forName(className);
MessagePlugin plugin = (MessagePlugin) cls.getDeclaredConstructor().newInstance();
plugin.sendMsg(new HashMap<>());
}5. Spring Boot Plugin Mechanism
Spring Boot extends the SPI idea with spring.factories. Define an interface, provide implementations, and list them in META-INF/spring.factories. Spring’s SpringFactoriesLoader reads the file and creates instances.
public interface SmsPlugin {
void sendMessage(String message);
}Two implementations:
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);
}
}spring.factories entry:
com.example.SmsPlugin=\
com.example.SystemSmsImpl,\
com.example.BizSmsImplLoad and use the plugins:
@GetMapping("/sendMsgV3")
public String sendMsgV3(String msg) {
List<SmsPlugin> smsServices = SpringFactoriesLoader.loadFactories(SmsPlugin.class, null);
for (SmsPlugin s : smsServices) {
s.sendMessage(msg);
}
return "success";
}6. Full Case Study
The article presents a realistic scenario with three micro‑services:
biz‑pp : defines MessagePlugin and publishes it as a JAR.
bitpt : Aliyun SMS implementation.
miz‑pt : Tencent SMS implementation.
The core module provides a PluginFactory that selects the appropriate implementation based on a configuration property ( msg.type=aliyun or tencent). A Spring Boot controller calls the factory, and the selected plugin sends the message.
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; // fallback to default implementation
}Maven dependencies include the core module and the two implementation modules. After packaging and starting the application, a request to localhost:8087/sendMsg?msg=hello triggers the selected SMS provider, as shown in the console output.
7. Conclusion
Plugin mechanisms are pervasive across languages, frameworks, and middleware. Understanding Java SPI, custom reflective loading, and Spring Boot’s spring.factories equips developers to design extensible, maintainable systems and to adapt quickly to changing business requirements.
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.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn 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.
