dubbo-扩展点-01

本文介绍了Java和Dubbo的SPI机制,探讨了SPI用于实现服务扩展的重要性。Java SPI通过配置文件加载服务实现,而Dubbo的SPI在JDK基础上进行了优化,支持按需加载、扩展自动包装、装配和自适应。文中通过实例展示了如何在Dubbo中使用和实现SPI,并分析了Dubbo SPI的源码,揭示了其高效加载和扩展性的特点。
摘要由CSDN通过智能技术生成


前言

文章是我在学习dubbo中间产生的一些疑问和自己的学习路程,记录下来,以后面试要是忘了可以回过头看看,哈哈哈!!!本篇文章讲一下我学习SPI的过程,至于为什么一上来就需要会这个玩意,说实话我也不知道,只是自己在看源码的时候冥冥之中觉得应该先看这个,至于和spring的扩展整合,暂时先不关心了,知道spring会把该做的做了就行了。

一、SPI机制

什么是SPI呢?(个人吐槽一下,虽然缩略以后好记,但是对于初学的人来说真的不知道讲的是什么?)
SPI全称Service Provider Interface,翻译过来就是:服务提供者接口。

为什么会有这么个东西出现呢?

我们知道在面向对象的设计里面提倡面向接口编程,这样做的好处就是不面向实现类修改代码的时候就会轻松许多,耦合不是那么明显;同时面向接口编程可以很多做到隐藏实现细节,因此,可以让服务提供者只需要遵循接口规范,具体的细节自己实现。但是不论是面向接口,还是面向具体实现都无法回避的一个问题就是,每次新增功能以后需要修改代码,做不到很多的扩展,因此,出现了SPI。

1.1、java的SPI机制

1.1.1、介绍

SPI使用起来很简单

  • MET-INF/services/文件夹中新建一个接口全路径名称的文件
  • 在文件内部写上具体实现的类的全路径
  • 使用java提供的ServiceClassLoader加载接口,会自动去路径下找到类,然后反射加载类进行实例化
  • 获得实现类对象以后调用需要用到的接口

好处就是:如果不需要一些实现类,我只需要在配置文件去掉实现类,或者我想新增功能只需要实现具体接口,然后在文件中增加实现类,将模块依赖进去就可以了。

1.1.2、具体实现

  • 新建文件接口全路径文件
    在这里插入图片描述
  • 文件内部编辑具体的实现类的全路径
    com.cloudwise.myactiviti.spi.DubboSpiService
    com.cloudwise.myactiviti.spi.JavaSpiService
    
  • 具体代码实现
    public static void main(String[] args) {
        ServiceLoader<SpiInterface> serviceLoader = ServiceLoader.load(SpiInterface.class);
        Iterator<SpiInterface> iterator = serviceLoader.iterator();
        while (iterator.hasNext()){
            SpiInterface next = iterator.next();
            //调用具体实现类的方法
            next.spi();
        }
    }
    

java SPI的缺点:无法实现按需加载,从上面的代码我们可以看出来,返回迭代器需要循环加载,导致很多我们不需要的类也加载了一遍,造成了内存的浪费,那么dubbo的SPI是如何实现的呢?

1.2、dubbo的SPI机制

1.2.1、介绍

为什么会有dubbo-spi?
一项技术的兴起肯定是为了解决原有技术不能提供的问题,那么dubbo-spi都改进了哪些问题呢?引自官网的一段话

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

由以上三点可以看出来,dubbo-spi是针对jdk的spi机制进行了优化以及增强,其实大致上的实现思路还是相似的,因此我们从jdk的spi机制入手开始分析dubbo-spi机制。

其实我们可以自己思考一下,为什么dubbo需要spi机制呢?
如果你看过源码,即使你没有看过源码,你只要用过dubbo,一般情况下会使用这种结构

在这里插入图片描述

当然,如果你看过源码,会看到如下这种结构,我们拿dubbo-rpc为例来看一下

dubbo-rpc目录结构
在这里插入图片描述
我们拿其中的两个协议,http和injvm来看
在这里插入图片描述
其实可以看出来,他们有共有的地方exporter、protocol、invoker,其实从dubbo整个工程结构可以看出来,dubbo一直都是支持可扩展机制的,不光是rpc,register也可以看出,可以有很多注册中心,在配置dubbo的时候,我们也可以指定多个协议或者多个配置中心,而不是把所有的协议或者注册中心都加载,因此jdk的spi机制就不能满足dubbo的需要,所以需要自己进行实现一套。

上面说了这么多,其实只是在说一件事,dubbo可以实现高扩展离不开dubbo-spi。同时dubbo-spi又不仅仅是实现了扩展那么简单,他还做了其他的事情,比如

  • 扩展自动包装XxxProtocolWrapper
  • 扩展自动装配
  • 扩展自适应
  • 扩展点自动激活

说了这么多我们还是先看看代码是如何实现的吧

1.2.2、具体实现

这里先不展开具体的如何扩展,先简单的看一下dubbo实现的spi如何使用,具体如何进行扩展开发后面会展开学习

如何使用呢?直接看源码怎么实现,怎么照着画一遍就行了

  • MET-INF/dubbo/internal/文件夹下新建接口全路径文件名的文件
    在这里插入图片描述
  • 内容填充需要扩展的具体实现的全路径
    myProtocol=com.cloudwise.myactiviti.spi.MyProtocol
    otherProtocol=com.cloudwise.myactiviti.spi.OtherProtocol
    
  • 编写自己的默认实现,继承Protocol接口
    public class MyProtocol implements Protocol {
        @Override
        public int getDefaultPort() {
            return 8089;
        }
    
        @Override
        public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
            return null;
        }
    
        @Override
        public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
            return null;
        }
    
        @Override
        public void destroy() {
    
    }
    
    public class OtherProtocol implements Protocol {
        @Override
        public int getDefaultPort() {
            return 8088;
        }
    
        @Override
        public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
            return null;
        }
    
        @Override
        public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
            return null;
        }
    
        @Override
        public void destroy() {
    
        }
    }
    
  • 使用dubbo提供的ExtensionLoader进行文件的加载
    public static void main(String[] args) {
        Protocol myProtocol =
                ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myProtocol");
        System.out.println(myProtocol.getDefaultPort());
    
    }
    

执行结果

14:32:38.890 [main] INFO org.apache.dubbo.common.logger.LoggerFactory - using logger: org.apache.dubbo.common.logger.slf4j.Slf4jLoggerAdapter
8089

Process finished with exit code 0

可以看出来是按需加载,其实从源码我们也可以看出来,跟踪进去getExtensio方法,我们可以看到

public T getExtension(String name, boolean wrap) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        } else if ("true".equals(name)) {
            return this.getDefaultExtension();
        } else {
            Holder<Object> holder = this.getOrCreateHolder(name);
            Object instance = holder.get();
            if (instance == null) {
                synchronized(holder) {
                    instance = holder.get();
                    if (instance == null) {
                        instance = this.createExtension(name, wrap);
                        holder.set(instance);
                    }
                }
            }

            return instance;
        }
    }

通过经典的双重检查创建单利bean,并且缓存起来,后面通过name进行获取,而不是一开始全部加载。

总结

主要学习了dubbo-spi产生原因,如何使用,简单的看了一下源码结构以及了解了一下dubbo的扩展机制,后面会学习一下本文中出现的自动包装,自动装配,自动适应,自动激活
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值