Spring Plugin
repository·main·Indexed 19 days ago
https://github.com/spring-projects/spring-pluginA lightweight, pragmatic plugin system for Java applications that provides extensibility through interface implementations discovered on the classpath. It integrates with the Spring framework's component model and provides tools like SimplePluginRegistry, BeanListBeanFactory, and the @EnablePluginRegistries annotation to manage, retrieve, and order plugins based on delimiters and metadata.
What's inside Spring Plugin
- Spring Plugin is a pragmatic, lightweight plugin system designed for building extensible Java architectures with minimal overhead. Unlike complex systems like OSGi, it does not provide dynamic class loading or runtime deployment. Instead, it allows you to extend a core system by providing implementations of dedicated plugin interfaces that are discovered via the classpath. It is designed to work seamlessly with the Spring framework by integrating into Spring's component model.
Order plugins using Spring Ordered capabilities
mainWhen using a
PluginRegistryconfigured via the Spring Plugin namespace or@EnablePluginRegistries, the registry is anOrderAwarePluginRegistry. This means it respects the ordering of plugins defined via standard Spring mechanisms:- Implementing the
org.springframework.core.Orderedinterface and overridinggetOrder(). - Using the
@Orderannotation on the plugin implementation class.
This is useful when you need to ensure certain plugins are executed before or after others in a host application.
- Implementing the
Capture plugin metadata with PluginMetadata and MetadataProvider
mainSpring Plugin provides a metadata module to capture essential information about plugin instances, such as
nameandversion. This metadata serves as a unique identifier for plugins within aPluginRegistry.There are two primary interfaces used to implement this concept:
PluginMetadata: Defines the core properties of a plugin (name and version).MetadataProvider: An interface that application plugin interfaces can implement to indicate they are capable of providing metadata.
Spring Plugin provides
SimplePluginMetadataas a standard Java bean-style implementation to capture these properties easily.public interface PluginMetadata { String getName(); String getVersion(); } public interface MetadataProvider { PluginMetadata getMetadata(); }When to use Spring Plugin instead of OSGi
mainSpring Plugin is an appropriate choice when you need to build a modular, extensible application but want to avoid the complexity of a full OSGi environment. Use Spring Plugin if:
- You want to minimize architectural overhead.
- You cannot or do not want to use OSGi's dynamic class loading or runtime installation features.
- You want to express extensibility through dedicated plugin interfaces.
- You want to extend functionality simply by adding JAR files containing interface implementations to your classpath.
- You are already using the Spring framework in your application (though this is not strictly required).
Collect Spring beans dynamically using BeanListBeanFactory
mainIn a Spring environment, you can use
BeanListBeanFactoryto automatically look up all beans of a specific type in theApplicationContextand register them as aListunder a specific name. This allows plugin implementations to be discovered dynamically without manual configuration of the host.To use this:
- Define a
BeanListBeanFactorybean. - Configure its
listsproperty with a map where the key is the desired bean ID and the value is the class type to look up. - Reference that bean ID in your host component.
If you are using Spring 2.5+ component scanning, plugin implementations annotated with
@Componentor@Servicewill be detected automatically.<!-- Configuration for the host --> <bean id="host" class="com.acme.HostImpl"> <property name="plugins" ref="plugins" /> </bean> <!-- Registering the dynamic list lookup --> <bean class="org.springframework.plugin.support.BeanListBeanFactory"> <property name="lists"> <map> <entry key="plugins" value="org.acme.MyPluginInterface" /> </map> </property> </bean>- Define a
Implement metadata-based plugin selection using AbstractMetadataBasedPlugin
mainTo simplify the creation of plugins that use metadata as selection criteria, you can extend
AbstractMetadataBasedPlugin.This base class automatically uses the plugin's internal metadata to implement the
supports(...)method from thePlugininterface. This pattern is useful when you need to select specific plugin instances based on external configuration, such as user-specific settings files.Configure plugin lists using the Spring Plugin XML namespace
mainTo reduce XML verbosity when configuring plugin lists, use the
pluginnamespace. This allows you to define a list of beans of a specific type in a single line using the<plugin:list>element.Namespace URI:
http://www.springframework.org/schema/plugin
XSD Location:https://www.springframework.org/schema/plugin/spring-plugin.xsd<beans xmlns="http://www.springframework.org/schema/beans" xmlns:plugin="http://www.springframework.org/schema/plugin" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/plugin https://www.springframework.org/schema/plugin/spring-plugin.xsd"> <!-- Define a plugin list as a top-level bean --> <plugin:list id="plugins" class="org.acme.MyPluginInterface" /> <!-- Or use it as an inner bean directly in a property --> <bean id="host" class="com.acme.HostImpl"> <property name="plugins"> <plugin:list class="org.acme.MyPluginInterface" /> </property> </bean> </beans>Implement the Plugin interface for conditional execution
mainThe
Plugin<S>interface allows plugin implementations to decide whether they should be invoked based on a provided delimiter of typeS.To implement a conditional plugin:
- Define an interface that extends
Plugin<S>. - Implement the
supports(S delimiter)method to returntrueif the plugin handles that specific delimiter. - Implement your core logic in the primary method (e.g.,
process()).
public enum ProductType { SOFTWARE, HARDWARE; } public interface ProductProcessor extends Plugin<ProductType> { public void process(Product product); } // Implementation example public class SoftwareProcessor implements ProductProcessor { @Override public boolean supports(ProductType productType) { return productType == ProductType.SOFTWARE; } @Override public void process(Product product) { // logic here } }- Define an interface that extends
Use PluginRegistry to access specific plugins
mainThe
PluginRegistry<T extends Plugin<S>, S>interface provides sophisticated methods to retrieve plugins that support a specific delimiter. You can useSimplePluginRegistryfor programmatic usage.Key Methods:
getPluginFor(S delimiter): Returns the first plugin supporting the delimiter, or anOptional.empty().getPluginOrDefaultFor(S delimiter, Supplier<T> defaultSupplier): Returns the first supporting plugin, or a provided default.getPluginsFor(S delimiter, Supplier<X extends RuntimeException> exceptionSupplier): Returns all plugins supporting the delimiter, or throws the provided exception if none are found.
PluginRegistry<ProductProcessor, ProductType> registry = SimplePluginRegistry.of(new FooImplementation()); // Returns the first plugin supporting SOFTWARE if available Optional<ProductProcessor> plugin = registry.getPluginFor(ProductType.SOFTWARE); // Returns the first plugin supporting SOFTWARE, or DefaultPlugin if none found ProductProcessor plugin = registry.getPluginOrDefaultFor(ProductType.SOFTWARE, () -> new DefaultPlugin()); // Returns all plugins supporting HARDWARE, throwing the given exception if none found List<ProductProcessor> plugins = registry.getPluginsFor(ProductType.HARDWARE, () -> new MyException("Damn!"));Enable PluginRegistries using @EnablePluginRegistries
mainInstead of XML configuration, you can use the
@EnablePluginRegistriesannotation to automatically register aPluginRegistryin the SpringApplicationContext.When you annotate a
@Configurationclass with@EnablePluginRegistries(MyPluginInterface.class), Spring registers anOrderAwarePluginRegistryfor that interface. The registered bean is namedmyPluginInterfaceRegistry(camelCase version of the interface name) and can be injected using@Qualifier.@Configuration @EnablePluginRegistries(MyPluginInterface.class) class ApplicationConfiguration { // ... } // To inject the registry elsewhere: @Autowired @Qualifier("myPluginInterfaceRegistry") private PluginRegistry<MyPluginInterface, SomeDelimiter> registry;Handle missing plugins with defaults or exceptions
mainWhen a plugin is required but not found,
SimplePluginRegistryprovides several ways to handle the absence of a match:Required Plugin (Throws Exception):
getRequiredPluginFor(S delimiter): Throws anIllegalArgumentExceptionwith a default error message if no plugin is found.getRequiredPluginFor(S delimiter, Supplier<String> message): Throws anIllegalArgumentExceptionwith a custom message provided by the supplier.getPluginFor(S delimiter, Supplier<E> ex): Returns the plugin or throws a custom exception provided by the supplier.
Default Plugin (Fallback):
getPluginOrDefaultFor(S delimiter, T plugin): Returns the matching plugin, or the providedplugininstance if no match is found.getPluginOrDefaultFor(S delimiter, Supplier<T> defaultSupplier): Returns the matching plugin, or the result of thedefaultSupplierif no match is found.
// Throw custom exception if not found MyPlugin plugin = registry.getPluginFor("delimiter", () -> new MyCustomException("Not found!")); // Fallback to a default plugin MyPlugin plugin = registry.getPluginOrDefaultFor("delimiter", defaultPlugin);Create a SimplePluginRegistry
mainUse
SimplePluginRegistryto manage a collection of plugins. It provides static factory methods to create registries from individual plugins, lists of plugins, or as an empty registry. It is a basic implementation that holds plugins in a list and uses thesupports(S delimiter)method of each plugin to determine which one matches a given input.// Create an empty registry SimplePluginRegistry<MyPlugin, String> registry = SimplePluginRegistry.empty(); // Create a registry with specific plugins SimplePluginRegistry<MyPlugin, String> registry = SimplePluginRegistry.of(plugin1, plugin2); // Create a registry from a list List<MyPlugin> pluginList = Arrays.asList(plugin1, plugin2); SimplePluginRegistry<MyPlugin, String> registry = SimplePluginRegistry.of(pluginList);