Dubbo SPI的可扩展机制

文章介绍了JavaSPI机制,它通过配置文件加载接口的实现类。接着讲解了DubboSPI,包括其Meta-INF/dubbo配置和@SPI注解的使用,以及Dubbo的IOC和AOP特性。DubboSPI在性能和功能上进行了优化,支持按需加载和依赖注入。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SPI 本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。(约定一个标准将实现类配置在一个地方,运行时动态为接口替换实现类)

一、Java SPI

定义一个接口类

public interface Robot {
    void sayHello();
}

定义两个实现类:

public class OptimusPrime implements Robot {
    
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}

META-INF/services 文件夹下创建名为org.apache.spi.Robot的文件

org.apache.spi.OptimusPrime
org.apache.spi.Bumblebee

编写Java代码测试类:

public class JavaSPITest {

    @Test
    public void sayHello() throws Exception {
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        System.out.println("Java SPI");
        serviceLoader.forEach(Robot::sayHello);
    }
}

运行结果:

JavaSPITest源码执行流程及2个重要方法:

hasNextService():

private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();//1.读取文件的位置
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);//2.加载配置文件
                } catch (IOException x) {
                }
            }
            //3.按行遍历文件内容
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

nextService():

       private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;//1 获取解析类的类名
            nextName = null;
            Class<?> c = null;
            try {
                //2、通过反射根据类名获取class对象
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {}
            try {
                //3、对象实例化
                S p = service.cast(c.newInstance());
                //4、放入缓存
                providers.put(cn, p);
                return p;
            } catch (Throwable x) { }
            throw new Error();         
        }

二、Dubbo SPI

META-INF/dubbo 创建文件

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。下面来演示 Dubbo SPI 的用法

public class DubboSPITest {

    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<Robot> extensionLoader = 
            ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}

Dubbo SPI源码执行流程:

  1. 通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例

  1. 通过 ExtensionLoader 的 getExtension 方法获取拓展类对象

2.1 通过 getExtensionClasses 获取所有的拓展类

2.2 clazz.newInstance(),通过反射创建拓展对象

2.3injectExtension(instance) 向拓展对象中注入依赖 (DUbbo的IOC)

2.4instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))将拓展对象包裹在相应的 Wrapper 对象中(Dubbo的AOP)

三、Dubbo IOC

Dubbo IOC 会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。如果有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            // 遍历目标类的所有方法
            for (Method method : instance.getClass().getMethods()) {
                // 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
                if (method.getName().startsWith("set")
                    && method.getParameterTypes().length == 1
                    && Modifier.isPublic(method.getModifiers())) {
                         ....省略代码.... 
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            // 通过反射调用 setter 方法设置依赖
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("fail to inject via method...");
                    }
                }
            }
        }
    } catch (Exception e) {
        ....省略代码.... 
    }
    return instance;
}

Dubbo IOC 目前仅支持 setter 方式注入

四、Dubbo AOP

循环遍历所有的Wrapper类,依次Wrapper对象进⾏依赖注⼊,返回最终的Wrapper对象(类似于俄罗斯套娃,一层套一层)

以DubboProtocol为例:

五、Dubbo SPI 与Java SPI区别

Java SPI

Dubbo SPI

  1. 一次遍历,获取全部元素,性能低

  1. 可以按需加载,性能比较高

  1. 支持IOC

  1. 支持AOP

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值