Service Provider Interface

Introduction

In order to improve scalability,EventMesh introduce the SPI(Service Provider Interface)mechanism, which can help to automatically find the concrete implementation class of the extended interface at runtime and load it dynamically. In EventMesh, all extension modules are implemented by using plugin. User can develop custom plugins by simply implementing extended interfaces, and select the plugin to be run at runtime by simply declare at configuration.

eventmesh-spi module

The implementation of SPI is at eventmesh-spi module, there are three main classes EventMeshSPI, EventMeshExtensionFactory and ExtensionClassLoader.

EventMeshSPI

EventMeshSPI is an SPI declaration annotation, all extended interface that want to be implemented should be declared by @EventMeshSPI.

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target({ElementType.TYPE})
  4. public @interface EventMeshSPI {
  5. /**
  6. * If true, the spi instance is singleton
  7. */
  8. boolean isSingleton() default false;
  9. }

Use annotation to declare the interface is an SPI extended interface can improve the readability of the code. On the other hand, @EventMeshSPI contains a isSingleton attribute which used to declare whether the extension instance is a singleton. If this attribute is true, that means the instance of this interface will be singleton.

EventMeshExtensionFactory

EventMeshExtensionFactory is a factory used to get the SPI extension instance which contains a static method getExtension(Class<T> extensionType, String extensionName).

  1. public enum EventMeshExtensionFactory {
  2. /**
  3. * @param extensionType extension plugin class type
  4. * @param extensionName extension instance name
  5. * @param <T> the type of the plugin
  6. * @return plugin instance
  7. */
  8. public static <T> T getExtension(Class<T> extensionType, String extensionName) {
  9. /* ... */
  10. }
  11. }

If you want to get the extension instance, you should use EventMeshExtensionFactory.

ExtensionClassLoader

ExtensionClassLoader is used to load extension instance classed, it has two subclass MetaInfExtensionClassLoader and JarExtensionClassLoader.

  1. /**
  2. * Load extension class
  3. * <ul>
  4. * <li>{@link MetaInfExtensionClassLoader}</li>
  5. * <li>{@link JarExtensionClassLoader}</li>
  6. * </ul>
  7. */
  8. public interface ExtensionClassLoader {
  9. /**
  10. * load
  11. *
  12. * @param extensionType extension type class
  13. * @param <T> extension type
  14. * @return extension instance name to extension instance class
  15. */
  16. <T> Map<String, Class<?>> loadExtensionClass(Class<T> extensionType);
  17. }

MetaInfExtensionClassLoader used to load class from classPath, and JarExtensionClassLoader used to load class from extension jar on the plugin directory. In the future, we might support the implementation to load from the maven repository.

SPI use case

The following is an example of how to use the SPI to declare a plugin.

First, we create an eventmesh-connector-api module, and define the extension interface MeshMQProducer, and we use @EventMeshSPI on the MeshMQProducer, which indicates the MeshMQProducer is an SPI interface.

  1. @EventMeshSPI(isSingleton = false)
  2. public interface MeshMQProducer extends Producer {
  3. /* ... */
  4. }

Then we create an eventmesh-connector-rocketmq module, which contains the concrete implementation named RocketMQProducerImpl.

  1. public class RocketMQProducerImpl implements MeshMQProducer {
  2. /* ... */
  3. }

At the same time, we need to create a file with the full qualified name of the SPI interface under the resource/META-INF/eventmesh directory in the eventmesh-connector-rocketmq module.

org.apache.eventmesh.api.producer.Producer

The content of the file is the extension instance name and the corresponding instance full class name

  1. rocketmq=org.apache.eventmesh.connector.rocketmq.producer.RocketMQProducerImpl

At this point, an SPI expansion module is complete. We can use EventMeshExtensionFactory.getExtension(MeshMQProducer.class, "rocketmq") to get the RocketMQProducerImpl instance.