手写简易版 Dubbo SPI(极简可运行)

我带你手写一个迷你版 Dubbo SPI,只保留核心底层原理

  • 注解 @SPI
  • 配置文件加载
  • 缓存机制
  • 根据 name 获取实现
  • IOC 注入(简化版)
  • AOP 包装(Wrapper)

代码量不到 300 行,运行即懂底层!


一、先定义核心注解(模拟 Dubbo)

1. @SPI 注解(标识扩展接口)

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SPI {
    // 默认扩展名称
    String value() default "";
}

2. @Adaptive 注解(自适应方法,简化版)

import java.lang.annotation.*;

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

二、定义测试接口 + 实现

1. 接口(加 @SPI)

@SPI("human")
public interface HelloService {
    void sayHello();
}

2. 实现类 A

public class HumanHello implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("你好,人类!");
    }
}

3. 实现类 B

public class RobotHello implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("Hello, Robot!");
    }
}

4. AOP 包装类(Wrapper,核心!)

// 包装类:必须持有目标接口对象 + 构造方法传入接口
public class HelloWrapper implements HelloService {
    private final HelloService delegate;

    public HelloWrapper(HelloService delegate) {
        this.delegate = delegate;
    }

    @Override
    public void sayHello() {
        System.out.println("===== 前置增强 =====");
        delegate.sayHello();
        System.out.println("===== 后置增强 =====");
    }
}

三、配置文件(固定路径,模拟 Dubbo)

创建文件:

resources/META-INF/dubbo/com.xxx.HelloService

内容:

human=com.xxx.HumanHello
robot=com.xxx.RobotHello
wrapper=com.xxx.HelloWrapper

四、手写核心类:MiniExtensionLoader(灵魂!)

这就是Dubbo ExtensionLoader 简化版,300 行内实现所有核心逻辑。

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;

/**
 * 手写迷你版 Dubbo ExtensionLoader
 * 实现:加载配置、缓存、实例化、IOC、AOP 包装
 */
public class MiniExtensionLoader<T> {
    // 1. 缓存:一个接口对应一个 ExtensionLoader
    private static final Map<Class<?>, MiniExtensionLoader<?>> LOADER_MAP = new ConcurrentHashMap<>();
    // 2. 缓存:name -> 扩展类 Class
    private final Map<String, Class<?>> extensionClasses = new ConcurrentHashMap<>();
    // 3. 缓存:name -> 扩展实例(最终对象)
    private final Map<String, T> extensionInstances = new ConcurrentHashMap<>();

    // 扩展接口
    private final Class<T> type;
    // 默认扩展名称
    private String defaultName;

    // 固定扫描路径
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

    // 私有构造,只能通过 getExtensionLoader 获取
    private MiniExtensionLoader(Class<T> type) {
        this.type = type;
        // 加载 SPI 注解
        SPI spi = type.getAnnotation(SPI.class);
        if (spi != null) {
            this.defaultName = spi.value();
        }
        // 加载配置文件
        loadExtensionClasses();
    }

    /**
     * 获取扩展加载器(入口)
     */
    public static <T> MiniExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null) throw new IllegalArgumentException("类型不能为空");
        if (!type.isInterface()) throw new IllegalArgumentException("必须是接口");
        if (!type.isAnnotationPresent(SPI.class)) throw new IllegalArgumentException("必须加 @SPI 注解");

        MiniExtensionLoader<T> loader = (MiniExtensionLoader<T>) LOADER_MAP.get(type);
        if (loader == null) {
            LOADER_MAP.putIfAbsent(type, new MiniExtensionLoader<>(type));
            loader = (MiniExtensionLoader<T>) LOADER_MAP.get(type);
        }
        return loader;
    }

    /**
     * 根据 name 获取扩展实例(核心方法)
     */
    public T getExtension(String name) {
        if (name == null || name.trim().isEmpty()) throw new IllegalArgumentException("名称不能为空");
        // 先查缓存
        T instance = extensionInstances.get(name);
        if (instance == null) {
            instance = createExtension(name);
            extensionInstances.put(name, instance);
        }
        return instance;
    }

    /**
     * 获取默认实例
     */
    public T getDefaultExtension() {
        return getExtension(defaultName);
    }

    /**
     * 创建扩展实例:实例化 -> IOC 注入 -> AOP 包装
     */
    private T createExtension(String name) {
        Class<?> clazz = extensionClasses.get(name);
        if (clazz == null) throw new IllegalArgumentException("未找到扩展:" + name);

        try {
            // 1. 实例化
            T instance = (T) clazz.newInstance();
            // 2. IOC 依赖注入(简化版)
            injectExtension(instance);
            // 3. AOP 包装(包装类增强)
            instance = wrapExtension(instance);
            return instance;
        } catch (Exception e) {
            throw new RuntimeException("创建实例失败", e);
        }
    }

    /**
     * 加载配置文件
     */
    private void loadExtensionClasses() {
        String fileName = DUBBO_DIRECTORY + type.getName();
        try {
            Enumeration<URL> 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;
                            // 解析 key=value
                            String[] kv = line.split("=");
                            String name = kv[0].trim();
                            String className = kv[1].trim();
                            Class<?> clazz = Class.forName(className);
                            extensionClasses.put(name, clazz);
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("加载配置失败", e);
        }
    }

    /**
     * IOC 注入(简化:模拟自动装配)
     */
    private void injectExtension(T instance) {
        // 真实 Dubbo 扫描 set 方法,这里简化
        // 你可以在这里写注入逻辑
    }

    /**
     * AOP 包装(核心:找到所有包装类,层层包装)
     */
    private T wrapExtension(T instance) {
        Class<?> clazz = instance.getClass();
        List<Class<?>> wrappers = new ArrayList<>();
        // 找到所有包装类(构造方法参数是当前接口)
        for (Class<?> c : extensionClasses.values()) {
            try {
                c.getConstructor(type);
                wrappers.add(c);
            } catch (NoSuchMethodException e) {
                // 不是包装类,跳过
            }
        }
        // 层层包装
        for (Class<?> wrapper : wrappers) {
            try {
                Constructor<?> constructor = wrapper.getConstructor(type);
                instance = (T) constructor.newInstance(instance);
            } catch (Exception e) {
                throw new RuntimeException("包装失败", e);
            }
        }
        return instance;
    }
}

五、测试运行(验证效果)

public class TestMiniSPI {
    public static void main(String[] args) {
        // 1. 获取扩展加载器
        MiniExtensionLoader<HelloService> loader = MiniExtensionLoader.getExtensionLoader(HelloService.class);

        // 2. 获取默认实现(human)
        HelloService human = loader.getDefaultExtension();
        human.sayHello();

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

        // 3. 获取指定实现(robot)
        HelloService robot = loader.getExtension("robot");
        robot.sayHello();
    }
}

运行结果

===== 前置增强 =====
你好,人类!
===== 后置增强 =====
------------------------
===== 前置增强 =====
Hello, Robot!
===== 后置增强 =====

完美实现 Dubbo SPI 核心:

  • 配置加载
  • 按 name 获取
  • 多级缓存
  • AOP 包装增强
  • 默认实现

六、手写版对应 Dubbo 底层原理

手写代码 Dubbo 真实源码 作用
@SPI @SPI 标识扩展接口 + 默认值
MiniExtensionLoader ExtensionLoader 核心加载器
LOADER_MAP EXTENSION_LOADERS 接口 → 加载器缓存
extensionClasses cachedClasses name → Class 缓存
extensionInstances cachedInstances name → 实例缓存
loadExtensionClasses() loadClass() 扫描加载配置
createExtension() createExtension() 实例化+注入+包装
wrapExtension() wrap() AOP 包装增强
META-INF/dubbo/ 相同路径 扩展配置文件

七、你能从手写版学到的核心

  1. Dubbo SPI 本质就是:配置 + 反射 + 缓存 + 包装
  2. 包装类(Wrapper)就是 AOP 的底层实现
  3. 一个接口一个 Loader,全缓存保证高性能
  4. getExtension(name) 流程:缓存 → 实例化 → 注入 → 包装

总结

这份手写极简版 Dubbo SPI,完全还原了Dubbo SPI 底层核心逻辑,去掉了复杂的异常处理、自适应代码,只保留最精髓的 300 行代码,看懂它 = 彻底懂 Dubbo SPI 底层

posted @ 2026-03-19 21:26  七星6609  阅读(2)  评论(0)    收藏  举报