sps4j is a lightweight and easy-to-use plugin framework designed for Java. It aims to help developers build modular applications where features can be developed, deployed, and managed as independent plugins without recompiling the main program. sps4j provides excellent integration support, especially for Spring Boot applications.
- Plugin Discovery and Lifecycle Management: Automatically discovers plugins from a specified path and manages their loading and unloading lifecycle.
- Isolated Class Loading: Uses a combination of Parent-First and Child-First class loading mechanisms to ensure the unity of core framework classes while isolating dependencies between plugins.
- Annotation-Driven: Define and declare a plugin with simple annotations (
@Sps4jPluginand@Sps4jPluginInterface). - Spring Boot Integration:
- The plugin itself can be a complete Spring Boot application (by extending
SpringBoot2AppPlugin). - Seamlessly integrates the plugin's web layer (e.g., Controllers) into the host application's Tomcat instance.
- Supports accessing beans from the host application within the plugin.
- The plugin itself can be a complete Spring Boot application (by extending
- Versioning: Plugins can declare a compatible version range with the host application, enabling smooth upgrades.
sps4j-annotation: Defines core annotations like@Sps4jPluginand@Sps4jPluginInterface, as well as annotation processors for compile-time processing.sps4j-common: Contains common utility classes used by the framework.sps4j-core: The core implementation of the framework, includingPluginManager, class loaders, and plugin lifecycle management.sps4j-spring-boot2: Provides the support layer for integration with Spring Boot 2.x, including adapters and auto-configuration to run plugins as Spring Boot applications.sps4j-plugin-parent: A Maven parent project that plugin projects can inherit from to simplify dependency management.sps4j-examples: Contains example code for using sps4j.
First, create a separate Maven module (e.g., greeter-api) to define the plugin interface. This module serves as the contract between the host application and the plugin implementations.
-
Add Maven Dependency:
<dependencies> <dependency> <groupId>io.github.qchole</groupId> <artifactId>sps4j-core</artifactId> <version>${sps4j.version}</version> <scope>provided</scope> </dependency> </dependencies>
-
Define the Plugin Interface:
import io.github.sps4j.annotation.Sps4jPluginInterface; import io.github.sps4j.core.Sps4jPlugin; @Sps4jPluginInterface("greeter") public interface GreeterPlugin extends Sps4jPlugin { String greet(String name); }
You can provide multiple implementations for the same interface. Just ensure that the name in each implementation's @Sps4jPlugin annotation is unique. For example:
-
Implementation 1:
hello-pluginInherit
sps4j-plugin-parentand add thegreeter-apidependency withprovidedscope in your Mavenpom.xml. Then, implement the interface.@Sps4jPlugin(name = "hello", version = "1.0.0", productVersionConstraint = ">=1.0") public class HelloPlugin implements GreeterPlugin { @Override public String greet(String name) { return "Hello, " + name + "!"; } }
-
Implementation 2:
bye-plugin@Sps4jPlugin(name = "bye", version = "1.0.0", productVersionConstraint = ">=1.0") public class ByePlugin implements GreeterPlugin { @Override public String greet(String name) { return "Bye, " + name + "!"; } }
In the host application module, configure the PluginManager and use the plugins.
-
Host Application
pom.xml:<dependencies> <dependency> <groupId>io.github.qchole</groupId> <artifactId>sps4j-core</artifactId> <version>${sps4j.version}</version> </dependency> <dependency> <groupId>io.github.qchole</groupId> <artifactId>greeter-api</artifactId> <version>1.0.0</version> </dependency> </dependencies>
-
Host Application Code:
public class Main { public static void main(String[] args) throws Exception { ProductPluginLoadService productService = () -> Version.parse("1.0.0"); File pluginDir = new File("plugins"); pluginDir.mkdirs(); PluginManager pluginManager = new DefaultPluginManager( pluginDir.toURI().toURL().toString(), productService, new DefaultPluginLoader() ); // Use getPluginsUnwrapped to get all "greeter" type plugins List<GreeterPlugin> allGreeters = pluginManager.getPluginsUnwrapped( GreeterPlugin.class, Collections.emptyMap() ); // Iterate and call all plugins System.out.println("Found " + allGreeters.size() + " greeter plugins."); for (GreeterPlugin plugin : allGreeters) { System.out.println(plugin.greet("World")); } } }
- Build the plugins: Run
mvn clean packagein thehello-pluginandbye-pluginmodules respectively. - Deploy the plugins: Copy the generated
hello-plugin-1.0.0.jarandbye-plugin-1.0.0.jarto a directory accessible by the host application. - Run the host application: Execute the
Main.mainmethod.Found 2 greeter plugins. Hello, World! Bye, World!
By leveraging the sps4j-spring-boot2 module, you can achieve deeper integration, allowing the plugin itself to be a Spring Boot application. Currently, Spring Boot 2.x is supported.
This step is identical to the basic usage. You need a separate greeter-api module to define the GreeterPlugin interface.
The plugin can not only implement business logic but also contain its own Controllers, Services, etc.
-
Maven Dependencies: Inherit
sps4j-plugin-parentand add thesps4j-spring-boot2dependency.<parent> <groupId>io.github.qchole</groupId> <artifactId>sps4j-plugin-parent</artifactId> <version>${sps4j.version}</version> </parent> <dependencies> <dependency> <groupId>io.github.qchole</groupId> <artifactId>greeter-api</artifactId> <version>1.0.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>io.github.qchole</groupId> <artifactId>sps4j-spring-boot2</artifactId> <scope>provided</scope> </dependency> </dependencies>
-
Implement the plugin interface and add annotations (The
@Sps4jPluginannotation'stagscan includeSpringBoot2AppPlugin.TAG_SPRING_MVC. This will start the plugin as a Spring MVC application, allowing you to expose web endpoints. Currently, only Tomcat is supported as the web server).@Sps4jPlugin( name = "spring-hello", version = "1.0.0", productVersionConstraint = ">=1.0", tags = {SpringBoot2AppPlugin.TAG_SPRING_MVC} // Mark as a Web application ) @SpringBootApplication public class SpringHelloPlugin extends SpringBoot2AppPlugin implements GreeterPlugin { @Override public String greet(String name) { return "Hello from Spring, " + name + "!"; } }
-
Add a Controller in the plugin:
@RestController public class PluginController { @GetMapping("/hello") public String handle() { return "This response comes from a controller inside the plugin!"; } }
-
Plugin's
application.yml: To avoid endpoint conflicts with the host application or other plugins, it's recommended to set a unique context path for each web plugin.server: servlet: context-path: /my-plugin
- Add the
sps4j-spring-boot2andgreeter-apidependencies to your Mavenpom.xml.<dependencies> <dependency> <groupId>io.github.qchole</groupId> <artifactId>sps4j-spring-boot2</artifactId> <version>${sps4j.version}</version> </dependency> <dependency> <groupId>io.github.qchole</groupId> <artifactId>greeter-api</artifactId> <version>1.0.0</version> </dependency> </dependencies>
- Add a main class.
@SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } }
- Create the
PluginManager.@Configuration public class PluginConfig { @Autowired private ResourceLoader resourceLoader; @Bean public ProductPluginLoadService productPluginLoadService() { return () -> Version.parse("1.0.0"); } @Bean public PluginManager loader(ProductPluginLoadService productPluginLoadService) throws IOException { Resource resource = resourceLoader.getResource("classpath:plugin"); return new DefaultPluginManager(resource.getURL().toString(), productPluginLoadService, new SpringAppSupportPluginLoader()); } }
- Load the plugin in the host application.
@RestController public class HostController { @Autowired private PluginManager pluginManager; @GetMapping("/load") public String load() { pluginManager.getPluginUnwrapped(GreeterPlugin.class, PluginArtifact.builder() .type("greeter").name("spring-hello").build(), Collections.emptyMap()); return "load ok "; } }
- Build the plugin: Run
mvn clean packagein the plugin project. - Deploy the plugin: Copy the plugin JAR to a directory accessible by the host application.
- Run the host application: Start the Spring Boot host application.
Since the plugin has a context path, all its endpoints are now under /my-plugin.
Open a browser or use curl to access http://localhost:8080/my-plugin/hello.
You will get the response:
This response comes from a controller inside the plugin!
This confirms that the plugin's web layer has been successfully integrated into the host application's service, running in its own namespace to avoid routing conflicts. Meanwhile, you can still get an instance of GreeterPlugin via PluginManager in the host application and call its methods.
A complete, runnable example can be found in the sps4j-examples/spring-boot2-example directory. This example includes a host application (host-application) and a plugin application (plugin-app), demonstrating all the steps described above.
- Clone the repository:
git clone https://github.com/qchole/sps4j.git - Navigate to the project root:
cd sps4j - Build with Maven:
mvn clean package