转载:http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235

SPI接口定义

定义了@SPI注解

public @interface SPI {

Stringvalue() default ""; //指定默认的扩展点

}

只有在接口打了@SPI注解的接口类才会去查找扩展点实现

会依次从这几个文件中读取扩展点

META-INF/dubbo/internal/   //dubbo内部实现的各种扩展都放在了这个目录了

META-INF/dubbo/

META-INF/services/

我们以Protocol接口为例, 接口上打上SPI注解,默认扩展点名字为dubbo

@SPI("dubbo")

public interface Protocol{

}

dubbo中内置实现了各种协议如:DubboProtocol InjvmProtocolHessianProtocol WebServiceProtocol等等

Dubbo默认rpc模块默认protocol实现DubboProtocol,key为dubbo

下面我们来细讲ExtensionLoader类

1.      ExtensionLoader.getExtensionLoader(Protocol.class)

每个定义的spi的接口都会构建一个ExtensionLoader实例,存储在

ConcurrentMap<Class<?>,ExtensionLoader<?>> EXTENSION_LOADERS 这个map对象中

2.      loadExtensionClasses 读取扩展点中的实现类

a)       先读取SPI注解的value值,有值作为默认扩展实现的key

b)       依次读取路径的文件

META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol

META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol

META-INF/services/ com.alibaba.dubbo.rpc.Protocol

3.      loadFile逐行读取com.alibaba.dubbo.rpc.Protocol文件中的内容,每行内容以key/value形式存储的。

a)     判断类实现(如:DubboProtocol)上有米有打上@Adaptive注解,如果打上了注解,将此类作为Protocol协议的设配类缓存起来,读取下一行;否则适配类通过javasisit修改字节码生成,关于设配类功能作用后续介绍

如果类实现没有打上@Adaptive, 判断实现类是否存在入参为接口的构造器(就是DubbboProtocol类是否还有入参为Protocol的构造器),有的话作为包装类缓存到此ExtensionLoader的Set<Class<?>>集合中,这个其实是个装饰模式

b)   如果类实现没有打上@Adaptive, 判断实现类是否存在入参为接口的构造器(就是DubbboProtocol类是否还有入参为Protocol的构造器),有的话作为包装类缓存到此ExtensionLoader的Set<Class<?>>集合中,这个其实是个装饰模式

c)     如果即不是设配对象也不是wrapped的对象,那就是扩展点的具体实现对象

查找实现类上有没有打上@Activate注解,有缓存到变量cachedActivates的map中

将实现类缓存到cachedClasses中,以便于使用时获取

4.      获取或者创建设配对象getAdaptiveExtension

a)如果cachedAdaptiveClass有值,说明有且仅有一个实现类打了@Adaptive, 实例化这个对象返回

b) 如果cachedAdaptiveClass为空, 创建设配类字节码。

为什么要创建设配类,一个接口多种实现,SPI机制也是如此,这是策略模式,但是我们在代码执行过程中选择哪种具体的策略呢。Dubbo采用统一数
据模式com.alibaba.dubbo.common.URL(它是dubbo定义的数据模型不是jdk的类),它会穿插于系统的整个执行过
程,URL中定义的协议类型字段protocol,会根据具体业务设置不同的协议。url.getProtocol()值可以是dubbo也是可以
webservice, 可以是zookeeper也可以是redis。

设配类的作用是根据url.getProtocol()的值extName,去ExtensionLoader. getExtension( extName)选取具体的扩展点实现。

所以能够利用javasist生成设配类的条件

1)接口方法中必须至少有一个方法打上了@Adaptive注解

2)打上了@Adaptive注解的方法参数必须有URL类型参数或者有参数中存在getURL()方法

下面给出createAdaptiveExtensionClassCode()方法生成javasist用来生成Protocol适配类后的代码

import com.alibaba.dubbo.common.extension;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {

//没有打上@Adaptive的方法如果被调到抛异常
      public void destroy() {

throw new UnsupportedOperationException(
  "methodpublic abstract void
com.alibaba.dubbo.rpc.Protocol.destroy() of
interfacecom.alibaba.dubbo.rpc.Protocol is not adaptive method!")

}

//没有打上@Adaptive的方法如果被调到抛异常
      public int getDefaultPort() {
             throw newUnsupportedOperationException(
             "method public
abstractint com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of
interfacecom.alibaba.dubbo.rpc.Protocol is not adaptive method!");
      }

//接口中export方法打上@Adaptive注册
      publiccom.alibaba.dubbo.rpc.Exporter export(
             com.alibaba.dubbo.rpc.Invokerarg0)  throws com.alibaba.dubbo.rpc.Invoker{
             if (arg0 == null)
                    throw newIllegalArgumentException("com.alibaba.dubbo.rpc.Invokerargument == null");
             //参数类中要有URL属性

if(arg0.getUrl() == null)
                    throw newIllegalArgumentException( "com.alibaba.dubbo.rpc.Invokerargument getUrl() == null");
             //从入参获取统一数据模型URL

com.alibaba.dubbo.common.URL url = arg0.getUrl();
           String extName =(url.getProtocol() == null ? "dubbo" : url.getProtocol());
           //从统一数据模型URL获取协议,协议名就是spi扩展点实现类的key

if (extName == null) throw new
IllegalStateException( "Fail to
getextension(com.alibaba.dubbo.rpc.Protocol) name from url("  +
url.toString() + ") usekeys([protocol])");

//利用dubbo服务查找机制根据名称找到具体的扩展点实现

com.alibaba.dubbo.rpc.Protocol extension =(com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);

//调具体扩展点的方法

return extension.export(arg0);
 }

//接口中refer方法打上@Adaptive注册
 publiccom.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0,
                    com.alibaba.dubbo.common.URLarg1) throws java.lang.Class {

//统一数据模型URL不能为空

if (arg1 == null)
             throw newIllegalArgumentException("url == null");

com.alibaba.dubbo.common.URL url =arg1;

//从统一数据模型URL获取协议,协议名就是spi扩展点实现类的key

String extName = (url.getProtocol() == null ?"dubbo" : url.getProtocol());
    if (extName == null)
       thrownewIllegalStateException("Failtogetextension(com.alibaba.dubbo.rpc.Protocol)name
from url("+ url.toString() + ") use keys([protocol])");

//利用dubbo服务查找机制根据名称找到具体的扩展点实现

com.alibaba.dubbo.rpc.Protocol
extension
=(com.alibaba.dubbo.rpc.Protocol)  ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class)
.getExtension(extName);
    //调具体扩展点的方法

return extension.refer(arg0, arg1);

}

}

5. 通过createAdaptiveExtensionClassCode()生成如上的java源码代码,要被java虚拟机加载执行必须得
编译成字节码,dubbo提供两种方式去执行代码的编译1)利用JDK工具类编译2)利用javassit根据源代码生成字节码。

如上图:

1)生成Adaptive代码code

2)利用dubbo的spi扩展机制获取compiler的设配类

3)编译生成的adaptive代码

在此顺便介绍下 @Adaptive注解打在实现类上跟打在接口方法上的区别

1)如果有打在接口方法上,调ExtensionLoader.getAdaptiveExtension()获取设配类,会先通过前面的过程生成 java的源代码,在通过编译器编译成class加载。但是Compiler的实现策略选择也是通过 ExtensionLoader.getAdaptiveExtension(),如果也通过编译器编译成class文件那岂不是要死循环下去了吗?

ExtensionLoader.getAdaptiveExtension(),对于有实现类上去打了注解@Adaptive的dubbo spi扩展机制,它获取设配类不在通过前面过程生成设配类java源代码,而是在读取扩展文件的时候遇到实现类打了注解@Adaptive就把这个类作为 设配类缓存在ExtensionLoader中,调用是直接返回

6.  自动Wrap上扩展点的Wrap类

这是一种装饰模式的实现,在jdk的输入输出流实现中有很多这种设计,在于增强扩展点功能。这里我们拿对于Protocol接口的扩

如图Protocol继承关系ProtocolFilterWrapper, ProtocolListenerWrapper这个两个类是装饰对象用来增强其他扩展点实现的功能。ProtocolFilterWrapper功能主要是在refer 引用远程服务的中透明的设置一系列的过滤器链用来记录日志,处理超时,权限控制等等功能;ProtocolListenerWrapper在provider的exporter,unporter服务和consumer 的refer服务,destory调用时添加监听器,dubbo提供了扩展但是没有默认实现哪些监听器。

Dubbo是如何自动的给扩展点wrap上装饰对象的呢?

1)在ExtensionLoader.loadFile加载扩展点配置文件的时候

对扩展点类有接口类型为参数的构造器就是包转对象,缓存到集合中去

2)在调ExtensionLoader的createExtension(name)根据扩展点key创建扩展的时候, 先实例化扩展点的实现, 在判断时候有此扩展时候有包装类缓存,有的话利用包转器增强这个扩展点实现的功能。如下图是实现流程

7. IOC大家所熟知的ioc是spring的三大基础功能之一, dubbo的ExtensionLoader在加载扩展实现的时候内部实现了个简单的ioc机制来实现对扩展实现所依赖的参数的注入,         dubbo对扩展实现中公有的set方法且入参个数为一个的方法,尝试从对象工厂ObjectFactory获取值注入到扩展点实现中去。

上图代码应该不能理解,下面我们来看看ObjectFactory是如何根据类型和名字来获取对象的,ObjectFactory也是基于dubbo的spi扩展机制的

它跟Compiler接口一样设配类注解@Adaptive是打在类AdaptiveExtensionFactory上的不是通过javassist编译生成的。

AdaptiveExtensionFactory持有所有ExtensionFactory对象的集合,dubbo内部默认实现的对象工厂是SpiExtensionFactory和SpringExtensionFactory,他们经过TreeMap排好序的查找顺序是优先先从SpiExtensionFactory获取,如果返回空在从SpringExtensionFactory获取。

1) SpiExtensionFactory工厂获取要被注入的对象,就是要获取dubbo spi扩展的实现,所以传入的参数类型必须是接口类型并且接口上打上了@SPI注解,返回的是一个设配类对象。

2) SpringExtensionFactory,Dubbo利用spring的扩展机制跟spring做了很好的融合。在发布或者去引用一个服务的时候,会把spring的容器添加到SpringExtensionFactory工厂集合中去, 当SpiExtensionFactory没有获取到对象的时候会遍历SpringExtensionFactory中的spring容器来获取要注入的对象

8. 下面 给出整体活动图

【DUBBO】 Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现的更多相关文章

  1. 2. Dubbo原理解析-Dubbo内核实现之基于SPI思想Dubbo内核实现(转)

    转载自  斩秋的专栏  http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577159 SPI接口定义 定义了@SPI注解 public ...

  2. Dubbo实现原理之基于SPI思想实现Dubbo内核

    dubbo中SPI接口的定义如下: @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public ...

  3. Dubbo 扩展点加载机制:从 Java SPI 到 Dubbo SPI

    SPI 全称为 Service Provider Interface,是一种服务发现机制.当程序运行调用接口时,会根据配置文件或默认规则信息加载对应的实现类.所以在程序中并没有直接指定使用接口的哪个实 ...

  4. 3. Dubbo原理解析-Dubbo内核实现之动态编译 (转)

    转载自  斩秋的专栏  http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577159 我们运行的Java代码,一般都是编译之后的字节码 ...

  5. Java进阶专题(二十六) 将近2万字的Dubbo原理解析,彻底搞懂dubbo

    前言 ​ 前面我们研究了RPC的原理,市面上有很多基于RPC思想实现的框架,比如有Dubbo.今天就从Dubbo的SPI机制.服务注册与发现源码及网络通信过程去深入剖析下Dubbo. Dubbo架构 ...

  6. dubbo源码解析-spi(3)

    前言 在上一篇的末尾,我们提到了dubbo的spi中增加了IoC和AOP的功能.那么本篇就讲一下这个增加的IoC,spi部分预计会有四篇,因为这东西实在是太重要了.温故而知新,我们先来回顾一下,我们之 ...

  7. Dubbo(一):Dubbo运行原理

    前言: 在开始入门Javaweb时,学的基本都是MVC开发模式,一个项目基本上就是model,view,controller三层.但是随着系统的服务逐渐加多,SOA模式更加适合目前项目开发.而SOA模 ...

  8. dubbo实现原理之动态编译

    Dubbo为了实现基于spi思想的扩展特性,特别是能够灵活添加额外功能,对于扩展或则策略选择的设配类能够动态生成.对于一些需求已知的类如Protocal,它们的设配类代码dubbo可以直接的提供,但是 ...

  9. dubbo 的 spi 思想是什么?

    面试题 dubbo 的 spi 思想是什么? 面试官心理分析 继续深入问呗,前面一些基础性的东西问完了,确定你应该都 ok,了解 dubbo 的一些基本东西,那么问个稍微难一点点的问题,就是 spi, ...

随机推荐

  1. 【三小时学会Kubernetes!(三) 】Service实践

    服务Service Kubernetes 服务资源可以作为一组提供相同服务的 Pod 的入口.这个资源肩负发现服务和平衡 Pod 之间负荷的重任,如图 16 所示. 图16:Kubernetes 服务 ...

  2. spring mvc:视图解析器

    ModelAndView对象中的view对象,可以使用字符串来让Spring框架进行解析获得适合的视图.而解析View的就是ViewResolver技术. ViewResolver的定义如下: pub ...

  3. winform版本自动更新

    我们在使用软件的时候经常会遇到升级版本,这也是Winform程序的一个功能,今天就大概说下我是怎么实现的吧(代码有点不完美有小BUG,后面再说) 先说下我的思路:首先在打开程序的时候去拿到我之前在网站 ...

  4. flask学习(十二):for循环遍历

    一. 字典的遍历 语法和python一样,可以使用items().keys().values().iteritems().iterkeys().itervalues() {% for k, v in ...

  5. Pycharm-professional-2017.2.3破解安装

    初次接触Python,大神推荐使用PyCharm IDE工具,作为小白初生牛犊不怕虎,上手就来最新版的,这也许不是最好的选择,但在以后慢慢琢磨深入之后,会选择适合自己的版本,现参考把安装过程分享出来. ...

  6. VS的 X64下的汇编编译

    参考博客 VS编译64位汇编时报错:error C4235: 使用了非标准扩展: 不支持在此结构上使用“_asm”关键字 在用VS2013编译内联汇编时,报如下错误: 错误    5    error ...

  7. day22 CMDB 基础部分 (一)

    参考博客: http://www.cnblogs.com/alex3714/articles/5420433.html

  8. 二十三、DBMS_METADATA(提供提取数据库对象的完整定义的接口)

    1.概述 作用:提供提取数据库对象的完整定义的接口.这些定义可以用XML或SQL DDL格式描述.提供两种类型接口:可编程控制的接口:用于Ad Hoc查询的简单接口. 2.包的组成 dbms_meta ...

  9. Spring的AOP介绍

    AOP:(Aspect-Orlented-Programming)面向切面编程,和面向对象是互相补充的.面向对象是横着编程,面向切面则是竖着编程. 1 2 3 4 @Before("exec ...

  10. CSS 清除浮动 clear 属性

    CSS 清除浮动 clear 属性用于设定元素哪一侧不允许有其他浮动元素(而并非取消元素的浮动). 可能的取值如下: 取值 说明 none 默认值,允许两侧都有浮动元素 left 左侧不允许有其他浮动 ...