dubbo源码—SPI
Java中的SPI
SPI,Service Provider Interface,java中提供的一种使程序可扩展的方式,系统定义好接口规范,供其他服务提供方实现,服务提供方将自己jar包META-INF/services下新建一个以接口全名称定义的文件,里面内容写上自己服务的实现的类名,每一行代表一个实现,服务使用方可以通过ServiceLoader.load加载所有的服务,然后判断可以使用的服务。具体可以参考reference
dubbo中的SPI
dubbo也使用了类似Java中的SPI机制,不过服务发现等都是dubbo自己实现的。
dubbo中的ServiceLoader:com.alibaba.dubbo.common.extension.ExtensionLoader
dubbo中的扩展声明配置文件的位置:
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
dubbo中的扩展声明配置文件的内容(比如com.alibaba.dubbo.rpc.Protocol):
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
文件里面都是name=value的格式,name就是该扩展的名称,value是实现的扩展类的全限定名城,以#开始的都是注释
注解
dubbo为了支持可扩展的SPI机制,有三个相关重要的注解
/**
* 写在可扩展的接口上面,表明该接口是支持SPI扩展的,有一个属性value表示默认的扩展类名称(就是配置文件中的name)
* 比如:Protocol上的@SPI("dubbo"),说明默认的Protocol的扩展是name为dubbo的扩展,
* 查找配置文件META_INFO/dubbo.internal/com.alibaba.dubbo.rpc.Protocol
* 对应的扩展类是com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
*/
com.alibaba.dubbo.common.extension.SPI
/**
* 写在类上面表示该类是默认的adapter,写在方法上表示该方法需要被adapt,
* 属性value表示需要被adapt成的扩展的key(在url中配 置的key)
* 比如com.alibaba.dubbo.common.threadpool.ThreadPool#getExecutor方法上面的注解:@Adaptive({Constants.THREADPOOL_KEY})
* 表示该方法返回的Executor是url中key是threadpool指定的扩展,如果url中没有配置则使用默认的扩展
*/
com.alibaba.dubbo.common.extension.Adaptive
/**
* 指定默认激活的扩展,属性group和value用来过滤(判断什么时候激活)
* 比如:com.alibaba.dubbo.rpc.filter.ExceptionFilter上的@Activate(group = Constants.PROVIDER)
* 该filter只有在provider一侧的时候才会被激活,也就是服务提供方的时候才会使用该filter
*/
com.alibaba.dubbo.common.extension.Activate
获取扩展
dubbo获取扩展的方式都是先获取extensionLoader,然后通过loader去加载对应的扩展
获取对应的extensionLoader的方法,每个扩展接口都有自己的extensionLoader实例,获取之后缓存在EXTENSION_LOADERS中
com.alibaba.dubbo.common.extension.ExtensionLoader#getExtensionLoader
获取扩展的方法主要有:
// 获取适配后的扩展
com.alibaba.dubbo.common.extension.ExtensionLoader#getAdaptiveExtension
// 获取active的扩展
com.alibaba.dubbo.common.extension.ExtensionLoader#getActivateExtension
// 获取默认配置的扩展
com.alibaba.dubbo.common.extension.ExtensionLoader#getDefaultExtension
// 根据name(配置文件中的key)获取扩展
com.alibaba.dubbo.common.extension.ExtensionLoader#getExtension
加载扩展的调用路径
com.alibaba.dubbo.common.extension.ExtensionLoader#getExtensionClasses
com.alibaba.dubbo.common.extension.ExtensionLoader#loadExtensionClasses
com.alibaba.dubbo.common.extension.ExtensionLoader#loadFile
getAdaptiveExtension
获取指定扩展接口的adaptive类,可以是动态生成的,也可以使用注解@Adaptivate指定
主要步骤
- 先尝试从缓存中get,没有再去load
- load的时候,先判断是否有SPI注解,如果有注解判断value值配置是否正确(因为value指定的是默认扩展,不允许指定多个默认值),如果正确的话设置字段cachedDefaultName为配置的默认扩展
- 然后从指定的三个位置(就是上面配置文件的三个位置)查找并加载所有的扩展
- 主要的load工作由loadFile完成
- 找到class之后,调用newInstance实例化
- 判断该类是否有类字段是其他扩展,如果有则从所有的扩展中查找到对应的扩展并注入到该实例中
- 如果第4步没有找到配置了的adaptiveClass,需要动态生成一个adaptiveClass,调用的是createAdaptiveExtensionClassCode方法生成动态代码
- 获取classLoader,默认是加载ExtensionLoader的类加载器,用来加载动态编译后的class
- 获取com.alibaba.dubbo.common.compiler.Compiler.class的AdaptiveExtension,用来编译动态生成的代码
- 返回进过类加载器加载的动态编译后的Class
loadFile的主要过程
- 查找classLoader,默认使用ExtensionLoader对应的类加载器
- 使用classLoader查找指定目录下所有以扩展接口全限定名命名的配置文件,如果找到了继续,如果没有找到直接返回
- 循环加载每个查找到的配置文件
- 针对每个配置文件,读入每一行配置,忽略空行和注释("#")
- 如果类上面有Adaptive注解,设置为cachedAdaptiveClass
- 判断该扩展类是否有包含扩展接口类型的构造方法,如果有,将该class加入到cachedWrapperClasses,将来会使用wrapper包裹其他
- 如果没有上面这样的构造方法,判断该类是否有Activate注解,如果有则加入cachedActivates
- 并将配置的该class添加到extensionClasses
createAdaptiveExtensionClassCode生成动态代码
该方法会生成一个实现了扩展接口的adaptive类,该类实现了接口的所有方法,对于没有Adaptive注解的方法注解抛出UnsupportedOperationException,如果有该注解会从url中找到需要适配到的扩展,然后调用适配到的扩展的对应的方法。比如ProxyFactory接口的getProxy方法,默认会去url中找key为proxy对应的value,如果没有配置proxy的话,默认的value是javassist,然后根据该value加载对应的扩展,默认扩展JavassistProxyFactory,然后调用JavassistProxyFactory#getProxy
- 判断给定扩展接口是否有被Adaptive注解的方法,如果没有就报错,如果有则继续
- 动态拼接包名、import等代码
- 循环每一个方法进行进行适配adapt
- 如果没有Adaptive注解,则在适配方法内部抛出UnsupportedOperationException
- 如果有Adaptive注解,主要找到需要使用哪一个扩展来适配,下面的代码主要在找这个扩展的key
- 先判断该方法的入参有没有URL类型的参数,如果有则使该url,
- 如果没有URL类型的参数,判断该方法的参数有没有get方法的返回值是URL,如果没有则报错
- 如果有返回值是URL类型入参,做一些NPE的校验,使用get方法获取url
- 上面已经获取到url,接下来是从url中获取配置该类扩展的名称(配置文件中name=value中的name),需要先知道url中对应的key
- 如果adaptive注解上配置了value属性则直接使用作为key
- 如果没有配置,则使用扩展点接口名的点分隔作为key
- 找到key之后就是在url中找到key对应的value,分以下几种情况:
- key是protocol,如果有默认扩展名称配置cachedDefaultName并且url.getProtocol()为空则使用cachedDefaultName,否则直接通过url.getProtocol()获取对应的扩展的name
- key不是protocol,如果参数中没有Invocation则直接冲url中获取;如果有Invocation需要获取方法级的配置(因为比如loadbalance,如果方法级有单独的配置,需要按照方法级的配置获取扩展)
- 注意:只有一个配置的时候才会考虑默认配置cachedDefaultName,如果有多个配置,以最后一个获取到的配置为准
- 取到扩展的name之后,调用ExtensionLoader#getExtension获取扩展
- 调用扩展的对应方法
下面是动态生成的接口com.alibaba.dubbo.rpc.ProxyFactory的适配类
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adpative implements com.alibaba.dubbo.rpc.ProxyFactory {
public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common
.URL arg2) throws com.alibaba.dubbo.rpc.RpcException {
if (arg2 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg2;
String extName = url.getParameter("proxy", "javassist");
if (extName == null) throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() +
") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(
com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getInvoker(arg0, arg1, arg2);
}
public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException(
"com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("proxy", "javassist");
if (extName == null) throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() +
") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(
com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0);
}
}
getActivateExtension
该方法主要获取两种扩展:
- 符合条件的配置了@Activate注解的扩展,从cachedActivates中查找
- 指定name的扩展
找到的所有扩展是有顺序的,如果用户有配置按照用户配置的先后顺序(默认activate的扩展的排序规则ActivateComparator)
从cachedActivates中查找的主要步骤是:
- getExtensionClasses加载所有的扩展,其中会将类上面有Activate的class加入cachedActivates
- 如果配置了"-default",表示不使用默认有Activate注解扩展,如果没有该配置,冲cachedActivates中查找合适的扩展眼,根据以下条件过滤默认activate的扩展,下面条件都符合才会添加到最终的返回值中
- 根据注解上配置的group是否和指定的一致
- url中配置不包含该扩展,url中也不包含去除该扩展name的配置,Activate注解配置了value并且url中的配置包含该value
- 将从cachedActivates中找到的扩展排序
查找指定name扩展的步骤是:
- 配置的name不是用来排除扩展的,并且没有没有排除该扩展的配置
- 如果指定的配置中包含默认配置的"default",将default前面的配置扩展放在list的前面,default后面的配置正常放在list的后面,保证按照用户配置的顺序获取对应的扩展
getDefaultExtension
获取默认的扩展,也是先加载该接口所有的扩展,这个过程中会将SPI注解配置了value——也就是默认的使用的扩展,赋值给cachedDefaultName,然后调用getExtension加载该nam对应的扩展
getExtension
根据给定的name来获取扩展的class,返回对应的实例对象
- 对name判空
- 如果name是true则getDefaultExtension获取默认扩展
- 尝试从缓存中获取holder,如果没有则先添加holder
- double check holder中是否有对应的实例对象,没有的话就调用createExtension创建,创建成功之后set到holder中
createExtension
根据名称获取扩展
- 加载该接口对应的所有扩展,查找该name对应扩展的class,如果没有该name的扩展则报错
- 从缓存中获取class,如果没有则newInstance,并加入缓存
- 属性注入:如果该实例对象的字段是扩展接口类型,查找所有扩展中该扩展的并将注入到实例对象中
- 使用wrapper包装:在loadFile加载扩展的时候会把符合条件(条件就是扩展类包含扩展接口作为入参的构造方法)的wrapper加入cachedWrapperClasses,将所有的wrapper应用到instance,并调用injectExtension注入wrapper中属性
总结
dubbo按照java中的SPI机制来保证扩展性,按照约定将对应的配置文件放在指定的目录下,通过读取配置件的方式来获取接口的扩展实现,可以使用动态编译的方法可以动态获取扩展的适配类。将所有实例化的类缓存起来可以保证单例而且加快速度,同时dubbo有简单的属性装配功能,在实例化扩展的时候会将属性也为扩展接口的字段进行注入。
dubbo源码—SPI的更多相关文章
- 【Dubbo 源码解析】02_Dubbo SPI
Dubbo SPI:(version:2.6.*) Dubbo 微内核 + 插件 模式,得益于 Dubbo SPI .其中 ExtentionLoader是 Dubbo SPI 最核心的类,它负责扩展 ...
- dubbo源码解析-spi(4)
前言 本篇是spi的第四篇,本篇讲解的是spi中增加的AOP,还是和上一篇一样,我们先从大家熟悉的spring引出AOP. AOP是老生常谈的话题了,思想都不会是一蹴而就的.比如架构设计从All in ...
- dubbo源码解析-spi(3)
前言 在上一篇的末尾,我们提到了dubbo的spi中增加了IoC和AOP的功能.那么本篇就讲一下这个增加的IoC,spi部分预计会有四篇,因为这东西实在是太重要了.温故而知新,我们先来回顾一下,我们之 ...
- dubbo源码解析-spi(一)
前言 虽然标题是dubbo源码解析,但是本篇并不会出现dubbo的源码,本篇和之前的dubbo源码解析-简单原理.与spring融合一样,为dubbo源码解析专题的知识预热篇. 插播面试题 你是否了解 ...
- 【Dubbo源码阅读系列】之 Dubbo SPI 机制
最近抽空开始了 Dubbo 源码的阅读之旅,希望可以通过写文章的方式记录和分享自己对 Dubbo 的理解.如果在本文出现一些纰漏或者错误之处,也希望大家不吝指出. Dubbo SPI 介绍 Java ...
- Dubbo源码分析之 SPI(一)
一.概述 dubbo SPI 在dubbo的作用是基础性的,要想分析研究dubbo的实现原理.dubbo源码,都绕不过 dubbo SPI,掌握dubbo SPI 是征服dubbo的必经之路. 本篇文 ...
- Dubbo源码解析之SPI(一):扩展类的加载过程
Dubbo是一款开源的.高性能且轻量级的Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用.智能容错和负载均衡,以及服务自动注册和发现. Dubbo最早是阿里公司内部的RPC框架,于 ...
- Dubbo源码剖析六之SPI扩展点的实现之Adaptive功能实现原理
接Dubbo源码剖析六之SPI扩展点的实现之getExtensionLoader - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)继续分析Adaptive功能实现原理.Adaptive的主 ...
- Dubbo源码剖析六之SPI扩展点的实现之getExtension
上文Dubbo源码剖析六之SPI扩展点的实现之getExtensionLoader - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中分析了getExtensionLoader,本文继续分 ...
随机推荐
- idea 配置tomcat(包含tomcat Server找不到的配置方法)
在配置tomcate时有时候按照网上说的找不到tomcat Server,不知不觉花了很长时间这时我们在这里配置就可以了如图所示 1.这是有tomcat Server的配置(后面是没有的情况下的配置) ...
- bash脚本条件测试总结
一.if语句的结构 分为以下三种:单分支.双分支.多分支 单分支if语句 if CONDITION is True: then 分支 fi 双分支if语句 if CONDITION is True: ...
- java中自动装箱带来的性能问题
之前没有特别注意自动封装所带来的性能问题,今天看了effective java,实验了一下,结果大吃一惊: 考虑下面这段代码: public static void main(String args[ ...
- windows服务器下iis的性能优化 服务器
IIS性能优化 1.调整IIS高速缓存 HKEY_LOCAL_MACHINE SystemCurrentControlSetServicesInetInfoParametersMemoryCacheS ...
- 使用composer更新thinkphp5或则yii2的版本
更新thinkphp5或则yii2的版本,我目前采用的是用composer去更新,小伙伴们如果有其他更好的办法更新,可以直接评论给我,不胜感激啊. 如果还没有安装 Composer ,你可以按 Com ...
- Python学习二:词典基础详解
作者:NiceCui 本文谢绝转载,如需转载需征得作者本人同意,谢谢. 本文链接:http://www.cnblogs.com/NiceCui/p/7862377.html 邮箱:moyi@moyib ...
- 最小化安装linux CentOS_7操作系统
实验环境为VMware虚拟机安装操作系统. 1.打开VMware Workstation 虚拟机,选择创建新的虚拟机: 2.选择linux-CentOS 64位操作系统: 3.为虚拟机命名,并选择安装 ...
- Spring JDBC 示例
在使用普通的 JDBC 数据库时,就会很麻烦的写不必要的代码来处理异常,打开和关闭数据库连接等.但 Spring JDBC 框架负责所有的低层细节,从开始打开连接,准备和执行 SQL 语句,处理异常, ...
- java 之 简单工厂模式(大话设计模式)
以前只是看设计模式,每次看完都去理解一次,并没有手动去写代码,所以理解的还不是很深刻,最近查看框架源码,发现很多地方用到的都是设计模式,因为对设计模式理解的不够深刻,所以源码查看进度很慢!现在决定来温 ...
- 流式处理的新贵 Kafka Stream - Kafka设计解析(七)
原创文章,转载请务必将下面这段话置于文章开头处. 本文转发自技术世界,原文链接 http://www.jasongj.com/kafka/kafka_stream/ Kafka Stream背景 Ka ...