Implementing a Simplified Dubbo SPI Mechanism

Core Annotations (Simulating Dubbo)

1. @SPI Annotation (Marking Extension Interfaces)

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SPI {
    // Default extension name
    String value() default "";
}

2. @Adaptive Annotation (Simplified Adaptive Methods)

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Adaptive {
    String[] value() default {};
}

Defining Test Interface + Implementations

1. Interface (with @SPI)

@SPI("human")
public interface GreetingService {
    void greet();
}

2. Implementation A

public class HumanGreeting implements GreetingService {
    @Override
    public void greet() {
        System.out.println("Hello, human!");
    }
}

3. Implementation B

public class RobotGreeting implements GreetingService {
    @Override
    public void greet() {
        System.out.println("Greetings, Robot!");
    }
}

4. AOP Wrapper Class (Core Concept)

// Wrapper class: must hold target interface object + constructor with interface parameter
public class GreetingWrapper implements GreetingService {
    private final GreetingService delegate;

    public GreetingWrapper(GreetingService delegate) {
        this.delegate = delegate;
    }

    @Override
    public void greet() {
        System.out.println("===== Pre-processing =====");
        delegate.greet();
        System.out.println("===== Post-processing =====");
    }
}

Configuration Files (Fixed Path, Simulating Dubbo)

Create file:
resources/META-INF/dubbo/com.example.GreetingService
Content:
human=com.example.HumanGreeting
robot=com.example.RobotGreeting
wrapper=com.example.GreetingWrapper

Core Class: SimpleExtensionLoader (The Heart of Implementation)

This is the simplified version of Dubbo's ExtensionLoader, implementing all core logic in under 300 lines.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Simplified Dubbo ExtensionLoader implementation
 * Features: configuration loading, caching, instantiation, IOC, and AOP wrapping
 */
public class SimpleExtensionLoader {
    // 1. Cache: one interface corresponds to one ExtensionLoader
    private static final Map, SimpleExtensionLoader> LOADER_CACHE = new ConcurrentHashMap<>();
    // 2. Cache: name -> extension class
    private final Map> extensionClasses = new ConcurrentHashMap<>();
    // 3. Cache: name -> extension instance (final object)
    private final Map extensionInstances = new ConcurrentHashMap<>();

    // Extension interface
    private final Class type;
    // Default extension name
    private String defaultExtension;

    // Fixed scanning path
    private static final String CONFIG_DIRECTORY = "META-INF/dubbo/";

    // Private constructor, only accessible via getExtensionLoader
    private SimpleExtensionLoader(Class type) {
        this.type = type;
        // Load SPI annotation
        SPI spiAnnotation = type.getAnnotation(SPI.class);
        if (spiAnnotation != null) {
            this.defaultExtension = spiAnnotation.value();
        }
        // Load configuration files
        loadExtensionClasses();
    }

    /**
     * Get extension loader (entry point)
     */
    public static  SimpleExtensionLoader getExtensionLoader(Class type) {
        if (type == null) throw new IllegalArgumentException("Type cannot be null");
        if (!type.isInterface()) throw new IllegalArgumentException("Must be an interface");
        if (!type.isAnnotationPresent(SPI.class)) throw new IllegalArgumentException("Must have @SPI annotation");

        SimpleExtensionLoader loader = (SimpleExtensionLoader) LOADER_CACHE.get(type);
        if (loader == null) {
            LOADER_CACHE.putIfAbsent(type, new SimpleExtensionLoader<>(type));
            loader = (SimpleExtensionLoader) LOADER_CACHE.get(type);
        }
        return loader;
    }

    /**
     * Get extension instance by name (core method)
     */
    public T getExtension(String name) {
        if (name == null || name.trim().isEmpty()) throw new IllegalArgumentException("Name cannot be empty");
        // Check cache first
        T instance = extensionInstances.get(name);
        if (instance == null) {
            instance = createExtension(name);
            extensionInstances.put(name, instance);
        }
        return instance;
    }

    /**
     * Get default instance
     */
    public T getDefaultExtension() {
        return getExtension(defaultExtension);
    }

    /**
     * Create extension instance: instantiate -> IOC injection -> AOP wrapping
     */
    private T createExtension(String name) {
        Class clazz = extensionClasses.get(name);
        if (clazz == null) throw new IllegalArgumentException("Extension not found: " + name);

        try {
            // 1. Instantiate
            T instance = (T) clazz.newInstance();
            // 2. IOC dependency injection (simplified)
            injectDependencies(instance);
            // 3. AOP wrapping (wrapper enhancement)
            instance = wrapInstance(instance);
            return instance;
        } catch (Exception e) {
            throw new RuntimeException("Failed to create instance", e);
        }
    }

    /**
     * Load configuration files
     */
    private void loadExtensionClasses() {
        String fileName = CONFIG_DIRECTORY + type.getName();
        try {
            Enumeration urls = getClass().getClassLoader().getResources(fileName);
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
                        String line;
                        while ((line = br.readLine()) != null) {
                            line = line.trim();
                            if (line.isEmpty() || line.startsWith("#")) continue;
                            // Parse key=value
                            String[] keyValue = line.split("=");
                            String name = keyValue[0].trim();
                            String className = keyValue[1].trim();
                            Class clazz = Class.forName(className);
                            extensionClasses.put(name, clazz);
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Failed to load configuration", e);
        }
    }

    /**
     * IOC injection (simplified: simulating auto-wiring)
     */
    private void injectDependencies(T instance) {
        // Real Dubbo scans setter methods, simplified here
        // You can implement injection logic here
    }

    /**
     * AOP wrapping (core: find all wrapper classes and wrap them layer by layer)
     */
    private T wrapInstance(T instance) {
        Class clazz = instance.getClass();
        List> wrappers = new ArrayList<>();
        // Find all wrapper classes (constructor parameter is current interface)
        for (Class c : extensionClasses.values()) {
            try {
                c.getConstructor(type);
                wrappers.add(c);
            } catch (NoSuchMethodException e) {
                // Not a wrapper class, skip
            }
        }
        // Wrap layer by layer
        for (Class wrapper : wrappers) {
            try {
                Constructor constructor = wrapper.getConstructor(type);
                instance = (T) constructor.newInstance(instance);
            } catch (Exception e) {
                throw new RuntimeException("Failed to wrap instance", e);
            }
        }
        return instance;
    }
}

Testing Implementation (Verifying Functionality)

public class SimpleSPITest {
    public static void main(String[] args) {
        // 1. Get extension loader
        SimpleExtensionLoader loader = SimpleExtensionLoader.getExtensionLoader(GreetingService.class);

        // 2. Get default implementation (human)
        GreetingService human = loader.getDefaultExtension();
        human.greet();

        System.out.println("------------------------");

        // 3. Get specific implementation (robot)
        GreetingService robot = loader.getExtension("robot");
        robot.greet();
    }
}

Output Results

===== Pre-processing =====
Hello, human!
===== Post-processing =====
------------------------
===== Pre-processing =====
Greetings, Robot!
===== Post-processing =====

Core Concepts in Simplified Dubbo SPI

Simplified Implementation Actual Dubbo Source Code Purpose
@SPI @SPI Mark extension interface + default value
SimpleExtensionLoader ExtensionLoader Core loader
LOADER_CACHE EXTENSION_LOADERS Interface → loader cache
extensionClasses cachedClasses name → Class cache
extensionInstances cachedInstances name → instance cache
loadExtensionClasses() loadClass() Scan and load configuration
createExtension() createExtension() Instantiate + inject + wrap
wrapInstance() wrap() AOP wrapper enhancement
META-INF/dubbo/ Same path Extension configuration files

Key Insights from This Implementation

  1. Dubbo SPI fundamentally consists of: configuration + reflection + caching + wrapping
  2. Wrapper classes are the underlying implementation of AOP
  3. One interface per loader with full caching ensures high performance
  4. The getExtension(name) process: cache → instantiate → inject → wrap

Tags: Dubbo SPI java Framework microservices

Posted on Fri, 08 May 2026 11:06:54 +0000 by bluejay002