Dubbo源码解析之:SPI

本文详细解析了Dubbo的SPI机制,包括SPI示例、Dubbo的改进点以及Dubbo SPI机制的各个关键步骤,如加载扩展点、自适应扩展的创建和依赖注入。Dubbo并未直接使用Java的SPI,而是对其进行了增强,支持了IOC和AOP,使得扩展点的加载更加灵活和高效。Dubbo SPI的核心是ExtensionLoader,它负责加载和管理扩展实现,同时也实现了自适应扩展和依赖注入等功能。
摘要由CSDN通过智能技术生成

 

写在前面:相信每个程序员都有一颗想看源码的心,但是经常被源码的复杂所吓倒,很多人都停留在想的层面。最近刚好没有什么事情,就打算自己看一下源码。选择dubbo来阅读的原因在于一直对Dubbo有好感,毕竟阿里出品必是精品,虽然Dubbo也曾经历过停更的风波,但好在后面又更新啦,文章使用的版本是2.5.4。GitHub链接https://github.com/apache/dubbo。选择tags的2.5.4。

导入idea中,可以参考https://blog.csdn.net/fenfenguai/article/details/80611648https://blog.csdn.net/hpchenqi_16/article/details/80955546步骤不完全一样

下面结合自己看的一些博客和自己的理解和大家分享一下Dubbo中无处不在的SPI(Service Provider Interface),SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。为什么要先介绍SPI呢?因为在看 dubbo 源代码时如果没有了解扩展点机制,那么看到代码就是一片凌乱。源码中大量运用了SPI。写的不好的地方也请大家包容。声明:内容存在引用一些博主的博客如下(当然还有参考一些别的细节知识点,如设计模式等知识,这里不再列出)。

https://juejin.im/post/5c0cd73b5188256dd7744858

https://www.jianshu.com/p/99f568df0f05

一、SPI 示例

1.1 Java SPI示例

首先定义一个接口,再定义两个实现类

public interface IService {
    public String sayHello();
	public String getScheme();
}
public class HBaseServiceImpl implements IService {
 
	@Override
    public String sayHello() {
		return "Hello HBase!!";
	}
 
	@Override
	public String getScheme() {
		return "HBase";
	}
 
}
public class HDFSServiceImpl implements IService {
 
	@Override
    public String sayHello() {
		return "Hello HDFS!!";
	}
 
	@Override
	public String getScheme() {
		return "hdfs";
	}
}
public class ServiceLoaderTest {
	public static void main(String[] args) {
		ServiceLoader<IService> serviceLoader = ServiceLoader.load(IService.class);
		for (IService iService : serviceLoader) {
			System.out.println(iService.getScheme()+"  =  "+iService.sayHello());
		}

	}
 
这里做些解释:java.util.ServiceLoader这个类来从配置文件中加载子类或者接口的实现类。
主要是从META-INF/services这个目录下的配置文件加载给定接口或者基类的实现,
ServiceLoader会根据给定的类的full name来在META-INF/services下面找对应的文件,
在这个文件中定义了所有这个类的子类或者接口的实现类,返回一个实例。

 尝试一下,用#注释和不注释运行结果的区别。

//可以看到ServiceLoader可以根据IService把定义的两个实现类找出来,返回一个ServiceLoader的实现,
//而ServiceLoader实现了Iterable接口,所以可以通过ServiceLoader来遍历所有在配置文件中定义的类的实例。
//从使用层面来说,就是运行时,动态给接口添加实现类.其实这有有点像IoC的思想,将装配的控制权移到程序之外.通过改变配置文件,我们就能动态的改变一个接口的实现类.
//SPI 就是这样一种基于接口编程+策略模式+配置文件,同时可供使用者根据自己的实际需要启用/替换模块具体实现的方案。

//那么很容易想到一个问题,比如我想新增一个接口的实现类XXXServiceImpl,这样的话光改配置文件也还是不行,还要预先包里面就有这个实现类才行啊.
//这就要用到javassist,也就是动态字节码技术.这样可以在运行时动态生成Java类,就不存在要预先把接口的实现类先在包里放好

//事实上我就算不用spi,我用spring的ioc也能通过配置文件或者注解动态的注入不同的实现类啊
//在dubbo的设计中,就不想强依赖Spring的IoC容器,但是自已造一个小的IoC容器,也觉得有点过度设计.另外dubbo是不需要依赖任何第三方库的,引用官方文档原话如下
//理论上 Dubbo 可以只依赖 JDK,不依赖于任何三方库运行,只需配置使用 JDK 相关实现策略

1.2 Dubbo SPI示例

首先定义一个接口,再定义两个实现类。注:这里我是在源码中的dubbo-test包下写的。新建工程写可能报错。

package com.alibaba.dubbo.examples.spi;

import com.alibaba.dubbo.common.extension.SPI;

@SPI
public interface Robot {
    void sayHello();
}

 这里可以看到有@SPI注解,可以自己尝试去掉会有什么现象,这里我先给出来

package com.alibaba.dubbo.examples.spi.impl;


import com.alibaba.dubbo.examples.spi.Robot;

public class Bumblebee implements Robot {

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


package com.alibaba.dubbo.examples.spi.impl;


import com.alibaba.dubbo.examples.spi.Robot;

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

 

package com.alibaba.dubbo.examples.spi;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class DubboSPITest {

    public static void main(String[] args) {
        ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }

}

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。

optimusPrime = com.alibaba.dubbo.examples.spi.impl.OptimusPrime
bumblebee = com.alibaba.dubbo.examples.spi.impl.Bumblebee

代码结构及结果截图如下.

 

Dubbo SPI 除了支持按需加载接口实现类,还增加了 IOC 和 AOP 等特性,这些特性将会在接下来的源码分析章节中一一进行介绍。

Dubbo SPI 的改进点

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

上面这段话,暂时看不懂也没关系,我们先放在这,到时候回过头来看也是可以的。

在 Dubbo 中,如果某个 interface 接口标记了 @SPI 注解,那么我们认为它是 Dubbo 中的一个扩展点。扩展点是 Dubbo SPI 的核心。

Dubbo SPI 机制详解

Dubbo 扩展点的加载

上文说到Java SPI有 /META-INF/services 这样一个目录。在这个目录下有一个以接口命名的文件,文件的内容为接口具体实现类的全限定名。在 Dubbo 中我们也能找到类似的设计。

  • META-INF/services/(兼容JAVA SPI)
  • META-INF/dubbo/(自定义扩展点实现)
  • META-INF/dubbo/internal/(Dubbo内部扩展点实现)

非常好~我们现在已经知道了从哪里加载扩展点了,再回忆一下,JAVA SPI是如何加载的。

ServiceLoader<DubboService> spiLoader = ServiceLoader.load(XXX.class);

类似的,在 Dubbo 中也有这样一个用于加载扩展点的类 ExtensionLoader,这个是不是跟 SPI 机制非常像,只是 java spi 机制是 java 后台帮你实现读取文件并对接具体的实现类,而 dubbo 是自己去读文件。我们先来看一段简短的代码。

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

MonitorFactory monitorFactory = ExtensionLoader.getExtensionLoader(MonitorFactory.class).getAdaptiveExtension();

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();

getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例。在 Dubbo 的实现里面用到了大量类似的代码片段,我们只需要提供一个 type ,即可获取该 type 的自适应(关于自适应的理解在后文会提到)扩展类。在获取对应自适应扩展类时,我们首先获取该类型的 ExtensionLoader。看到这里我们应该下意识的感觉到对于每个 type 来说,都应该有一个对应的 ExtensionLoader 对象。我们先来看看 ExtensionLoader 是如何获取的。很自然想到应该是getXXX方法。

getExtensionLoader()

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null)//拓展点类型非空判断
            throw new IllegalArgumentException("Extension type == null");
        if (!type.isInterface()) {//拓展点类型只能是接口
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        //拓展点类型(接口)是否使用@SPI("xxx")注解标识
        if (!withExtensionAnnotation(type)) {//需要添加spi注解,否则抛异常
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        
智慧旅游解决方案利用云计算、物联网和移动互联网技术,通过便携终端设备,实现对旅游资源、经济、活动和旅游者信息的智能感知和发布。这种技术的应用旨在提升游客在旅游各个环节的体验,使他们能够轻松获取信息、规划行程、预订票务和安排食宿。智慧旅游平台为旅游管理部门、企业和游客提供服务,包括政策发布、行政管理、景区安全、游客流量统计分析、投诉反馈等。此外,平台还提供广告促销、库存信息、景点介绍、电子门票、社交互动等功能。 智慧旅游的建设规划得到了国家政策的支持,如《国家中长期科技发展规划纲要》和国务院的《关于加快发展旅游业的意见》,这些政策强调了旅游信息服务平台的建设和信息化服务的重要性。随着技术的成熟和政策环境的优化,智慧旅游的时机已经到来。 智慧旅游平台采用SaaS、PaaS和IaaS等云服务模式,提供简化的软件开发、测试和部署环境,实现资源的按需配置和快速部署。这些服务模式支持旅游企业、消费者和管理部门开发高性能、高可扩展的应用服务。平台还整合了旅游信息资源,提供了丰富的旅游产品创意平台和统一的旅游综合信息库。 智慧旅游融合应用面向游客和景区景点主管机构,提供无线城市门户、智能导游、智能门票及优惠券、景区综合安防、车辆及停车场管理等服务。这些应用通过物联网和云计算技术,实现了旅游服务的智能化、个性化和协同化,提高了旅游服务的自由度和信息共享的动态性。 智慧旅游的发展标志着旅游信息化建设的智能化和应用多样化趋势,多种技术和应用交叉渗透至旅游行业的各个方面,预示着全面的智慧旅游时代已经到来。智慧旅游不仅提升了游客的旅游体验,也为旅游管理和服务提供了高效的技术支持。
智慧旅游解决方案利用云计算、物联网和移动互联网技术,通过便携终端设备,实现对旅游资源、经济、活动和旅游者信息的智能感知和发布。这种技术的应用旨在提升游客在旅游各个环节的体验,使他们能够轻松获取信息、规划行程、预订票务和安排食宿。智慧旅游平台为旅游管理部门、企业和游客提供服务,包括政策发布、行政管理、景区安全、游客流量统计分析、投诉反馈等。此外,平台还提供广告促销、库存信息、景点介绍、电子门票、社交互动等功能。 智慧旅游的建设规划得到了国家政策的支持,如《国家中长期科技发展规划纲要》和国务院的《关于加快发展旅游业的意见》,这些政策强调了旅游信息服务平台的建设和信息化服务的重要性。随着技术的成熟和政策环境的优化,智慧旅游的时机已经到来。 智慧旅游平台采用SaaS、PaaS和IaaS等云服务模式,提供简化的软件开发、测试和部署环境,实现资源的按需配置和快速部署。这些服务模式支持旅游企业、消费者和管理部门开发高性能、高可扩展的应用服务。平台还整合了旅游信息资源,提供了丰富的旅游产品创意平台和统一的旅游综合信息库。 智慧旅游融合应用面向游客和景区景点主管机构,提供无线城市门户、智能导游、智能门票及优惠券、景区综合安防、车辆及停车场管理等服务。这些应用通过物联网和云计算技术,实现了旅游服务的智能化、个性化和协同化,提高了旅游服务的自由度和信息共享的动态性。 智慧旅游的发展标志着旅游信息化建设的智能化和应用多样化趋势,多种技术和应用交叉渗透至旅游行业的各个方面,预示着全面的智慧旅游时代已经到来。智慧旅游不仅提升了游客的旅游体验,也为旅游管理和服务提供了高效的技术支持。
深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值