Spring Plugin

repository·main·Indexed 19 days ago

https://github.com/spring-projects/spring-plugin

A 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.

Tokens
3.7K
Snippets
9
Records
13
Agent score
17%

What's inside Spring Plugin

  1. Overview of Spring Plugin

    main
    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.
  2. Order plugins using Spring Ordered capabilities

    main

    When using a PluginRegistry configured via the Spring Plugin namespace or @EnablePluginRegistries, the registry is an OrderAwarePluginRegistry. This means it respects the ordering of plugins defined via standard Spring mechanisms:

    1. Implementing the org.springframework.core.Ordered interface and overriding getOrder().
    2. Using the @Order annotation 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.

  3. Capture plugin metadata with PluginMetadata and MetadataProvider

    main

    Spring Plugin provides a metadata module to capture essential information about plugin instances, such as name and version. This metadata serves as a unique identifier for plugins within a PluginRegistry.

    There are two primary interfaces used to implement this concept:

    1. PluginMetadata: Defines the core properties of a plugin (name and version).
    2. MetadataProvider: An interface that application plugin interfaces can implement to indicate they are capable of providing metadata.

    Spring Plugin provides SimplePluginMetadata as a standard Java bean-style implementation to capture these properties easily.

    public interface PluginMetadata {
      String getName();
      String getVersion();
    }
    
    public interface MetadataProvider {
      PluginMetadata getMetadata();
    }
  4. When to use Spring Plugin instead of OSGi

    main

    Spring 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).
  5. Collect Spring beans dynamically using BeanListBeanFactory

    main

    In a Spring environment, you can use BeanListBeanFactory to automatically look up all beans of a specific type in the ApplicationContext and register them as a List under a specific name. This allows plugin implementations to be discovered dynamically without manual configuration of the host.

    To use this:

    1. Define a BeanListBeanFactory bean.
    2. Configure its lists property with a map where the key is the desired bean ID and the value is the class type to look up.
    3. Reference that bean ID in your host component.

    If you are using Spring 2.5+ component scanning, plugin implementations annotated with @Component or @Service will 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>
  6. Implement metadata-based plugin selection using AbstractMetadataBasedPlugin

    main

    To 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 the Plugin interface. This pattern is useful when you need to select specific plugin instances based on external configuration, such as user-specific settings files.

  7. Configure plugin lists using the Spring Plugin XML namespace

    main

    To reduce XML verbosity when configuring plugin lists, use the plugin namespace. 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>
  8. Implement the Plugin interface for conditional execution

    main

    The Plugin<S> interface allows plugin implementations to decide whether they should be invoked based on a provided delimiter of type S.

    To implement a conditional plugin:

    1. Define an interface that extends Plugin<S>.
    2. Implement the supports(S delimiter) method to return true if the plugin handles that specific delimiter.
    3. 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
      }
    }
  9. Use PluginRegistry to access specific plugins

    main

    The PluginRegistry<T extends Plugin<S>, S> interface provides sophisticated methods to retrieve plugins that support a specific delimiter. You can use SimplePluginRegistry for programmatic usage.

    Key Methods:

    • getPluginFor(S delimiter): Returns the first plugin supporting the delimiter, or an Optional.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!"));
  10. Enable PluginRegistries using @EnablePluginRegistries

    main

    Instead of XML configuration, you can use the @EnablePluginRegistries annotation to automatically register a PluginRegistry in the Spring ApplicationContext.

    When you annotate a @Configuration class with @EnablePluginRegistries(MyPluginInterface.class), Spring registers an OrderAwarePluginRegistry for that interface. The registered bean is named myPluginInterfaceRegistry (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;
  11. Handle missing plugins with defaults or exceptions

    main

    When a plugin is required but not found, SimplePluginRegistry provides several ways to handle the absence of a match:

    1. Required Plugin (Throws Exception):

      • getRequiredPluginFor(S delimiter): Throws an IllegalArgumentException with a default error message if no plugin is found.
      • getRequiredPluginFor(S delimiter, Supplier<String> message): Throws an IllegalArgumentException with 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.
    2. Default Plugin (Fallback):

      • getPluginOrDefaultFor(S delimiter, T plugin): Returns the matching plugin, or the provided plugin instance if no match is found.
      • getPluginOrDefaultFor(S delimiter, Supplier<T> defaultSupplier): Returns the matching plugin, or the result of the defaultSupplier if 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);
  12. Create a SimplePluginRegistry

    main

    Use SimplePluginRegistry to 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 the supports(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);