dubbo SPI之@SPI、@Adaptive注解, 以及什么时候动态生成$Adaptive代码
本文基于dubbo2.7.7对如下三个问题分析
@SPI
注解的作用@Adaptive
注解的作用,放在Type和Method上的区别和注意点- 什么时候动态生成和编译
xxx$Adaptive
代码
@SPI
注解
- dubbo中自定义了
SPI
机制,将接口和具体实现类分开,一个SPI接口可能有多种实现,用户也可以通过SPI实现自己的扩展。在META-INF/dubbo/internal
、META-INF/dubbo
、META-INF/services
三个目录下选择一个,新建名为接口全类名的文件,并在文件中填写扩展名extName和具体实现类, 扩展名任意, 但不要重复. - 注意: dubbo在扫描SPI的实现类时内部是通过Map保存,无法保证顺序. 但可以按照如下顺序,
META-INF/dubbo/internal
、META-INF/dubbo
、META-INF/services
优先级从高到低使用相同扩展名, 覆盖默认实现。
dubbo通过ExtensionLoader
来加载这些实现类。dubbo的spi接口必须要加上的@SPI
注解, 否则在加载时会抛出异常:
@SPI
注解上的value值用于指定默认实现的名字, 当加载实现类之前, 会先从接口的@SPI
注解上获取value值,如果未指定,调用ExtensionLoader.getExtensionLoader(CustomInterface.class).getDefaultExtension()
返回的是null
@Adaptive
注解
ExtensionLoader#getAdaptiveExtension();
是dubbo源码中使用比较多的方案,主要用于在spi多个实现类中找一个数据合适的扩展实现。在dubbo中该方法返回的实现类可以理解为是spi接口的一个代理类,是调用其它实现类的入口。
而@Adaptive
注解则是用来帮助ExtensionLoader#getAdaptiveExtension();
判断要获取哪个实现类.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {String[] value() default {};
}
- value指定的值只有在@Adaptive放在interface的method上时才会有作用, 放在具体实现类class上时根本不会读取该value的值
- value的值即为key名称,dubbo会通过key名称查找对应的扩展名extName,例如
String extName = url.getParameter(“transporter”, “netty”),
但对部分spi接口做了特殊处理,例如Protocol,不是调用getParameter()方法获取extName, 如下:
String extName = (url.getProtocol() == null ? “dubbo” : url.getProtocol()); - value可以指定多个,按照数组顺序, 优先级从高到底, 直到查到为止.
如果没有找到value中对应的扩展, 则使用接口的SimpleName, 以驼峰处添加点分隔符,字符串全部转成小写,作为参数获取获取extName例如org.apache.dubbo.xxx.YyyInvokerWrapper, 转成 yyy.invoker.wrapper - 调用ExtensionLoader#getExtension(java.lang.String extName)方法获取该扩展名对应的具体实例对象,未获取到则报错
从@Adaptive
注解定义可以看出既可以放在类上, 又可以放在方法上. 但却有着不同的意义和响应的限制
用在SPI实现类上作用及注意点
- 只能放在实现类上,放在spi接口上不会被dubbo处理,无意义;
- 如果多个实现类有
@Adaptive
注解, 会更具SPI目录的优先级覆盖;但同在META-INF/dubbo/internal
目录下,同类名的话则报错;如果已经有一个带有@Adaptive
注解的实现类,再调用ExtensionLoader#addExtension
则报错.
这样设计的原因其实很简单:只能有一个实现类带有@Adaptive
注解, dubbo内部带有@Adaptive
注解的实现类又希望可以被用户覆盖. - 放在实现类上时,则不会再动态生成
xxx$Adaptive
类。实际上dubbo源码中也就是org.apache.dubbo.common.compiler.support.AdaptiveCompiler
和org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
这样使用, 其它都是放在SPI接口方法上.
用在SPI接口方法上作用及注意点
- 放在SPI接口的方法上
@Adaptive
注解中的value属性,才会被处理生效 - 动态生成
xxx$Adaptive
类,动态编译class,例如:org.apache.dubbo.rpc.Protocol
, 则生成Protocol$Adaptive
源码并编译成class - 当实现类和接口都不存在
@Adaptive
时,调用#getAdaptiveExtension();
则会报如下异常:
什么时候动态生成$Adaptive代码
ExtensionLoader
在从META-INF/dubbo/internal
、META-INF/dubbo
、META-INF/services
三个目录加载SPI实现类时,如果实现类上存在@Adaptive
注解,则会被缓存到成员变量cachedAdaptiveClass
,当调用ExtensionLoader#getAdaptiveExtension()
会先判断实例是否存在,不存在则通过cachedAdaptiveClass
创建对象,如果cachedAdaptiveClass
的仍然为空,则更具SPI接口动态创建xxx$Adaptive
代码,xxx代表具体的SPI接口。此时要求接口中必须有@Adaptive
注解的方法, 否则报错. 参见源码:org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator
org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generate()
动态生成的$Adaptive代码内部是什么逻辑
对方法参数进行null值的一些常规校验, 然后根据@Adaptive注解的value属性值获取SPI扩展名和方法返回值类型获取对应的SPI实现类。
例如org.apache.dubbo.rpc.Protocol
接口动态生成的Protocol$Adaptive
代码如下:
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {public java.util.List getServers() {throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");}public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {if (arg1 == null)throw new IllegalArgumentException("url == null");org.apache.dubbo.common.URL url = arg1;String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );if(extName == null)throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);return extension.refer(arg0, arg1);}public void destroy() {throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");}public int getDefaultPort() {throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");}public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {if (arg0 == null)throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");if (arg0.getUrl() == null)throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");org.apache.dubbo.common.URL url = arg0.getUrl();String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );if(extName == null)throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);return extension.export(arg0);}
}
dubbo SPI之@SPI、@Adaptive注解, 以及什么时候动态生成$Adaptive代码相关推荐
- Dubbo源码分析(三)Dubbo中的SPI和自适应扩展机制
前言 我们在往期文章中,曾经深入分析过Java的SPI机制,它是一种服务发现机制.具体详见:深入理解JDK的SPI机制 在继续深入Dubbo之前,我们必须先要明白Dubbo中的SPI机制.因为有位大神 ...
- 【七夕特殊礼物】Dubbo学习之SPI实战与debug源码
目录 绪论 环境搭建 dubbo-demo-interface dubbo-demo-xml dubbo-demo-xml-provider 源码跟踪 getExtension createExten ...
- spring-boot框架dubbo在controlle中r注解@Reference注入service,但是调用方法时候,service报null空指针异常
spring-boot框架dubbo在controlle中r注解@Reference注入service,但是调用方法时候,service报null空指针异常 参考文章: (1)spring-boot框 ...
- 【原创】【SPI】SPI通信协议介绍
1.这是个什么玩意 接上篇接着介绍,协议主要就是保证双方能够正常的通信并理解对方的"话".而本篇介绍的这个SPI协议是为了保证SPI接口的两头双方能够正常通信.具体的说,就是针对那 ...
- 【SPI】SPI学习之SPI硬件相关
关联内容: [SPI]SPI学习之SPI硬件相关 [SPI]SPI学习之SPI驱动相关 [SPI]SPI学习之SPI调试相关 目录 SPI硬件知识 SPI相关的缩写或说法 SPI外部信号描述 SPI硬 ...
- linux内核添加spi驱动,Linux内核驱动之spi子系统spi协议.docx
Linux内核驱动之spi子系统spi协议 概况 SPI接口是摩托罗拉首先提出的全双工三线同步串行外围接口SCK,MOSI,MISO,采用主从模式(Master Slave)架构:支持多slave模式 ...
- STM32L475 硬件SPI+软件SPI驱动ST7789V2
前言 最近购买了IoT Board 潘多拉开发板来研究,学习使用STM32CubeMX工具配置SPI,然后驱动了TFTLCD.潘多拉开发板的TFTLCD驱动IC是ST7789V2,结合原子哥的TFTL ...
- 【SPI】SPI学习之SPI驱动相关
关联内容: [SPI]SPI学习之SPI硬件相关 [SPI]SPI学习之SPI驱动相关 [SPI]SPI学习之SPI调试相关 目录 spi驱动框架 SPI控制器 spi_master 结构体 spi主 ...
- Mybatis整体学习笔记-CRUD-配置解析-结果集映射-日志-注解开发-复杂环境-动态SQL-缓存
MyBatis 要多对官方文档进行学习 https://mybatis.org/mybatis-3/zh/index.html 简介 MyBatis 持久层框架 Dao Access Objects ...
最新文章
- 数据科学家所需的大脑训练
- Struts2官网翻译
- PHP扩展CURL的用法详解
- 怎么解决深入学习PHP的瓶颈
- 【DP】Rotating Substrings(CF1363F)
- 设计师职场提升效率品质素材网,一个足矣
- validatebox自定义验证规则以及使用
- Spring与Struts2的整合
- 20200703每日一句
- csgo自建局域网服务器,csgo怎么创建局域网游戏
- 解决win10中无法打开CHM文件的方法
- Matlab中zeros和ones函数用法
- 约束布局ConstraintLayout ,报错:This view is not constrained vertically
- 3dsmax 2019 插件开发要点
- 10 款值得珍藏的 Chrome 浏览器插件
- 使用GPG验证文件签名
- CentOS8 Nomad安装(2)
- 加密交易所的新战场:高频交易
- python+百度翻译api制作中英文互转的代码应用实例
- 常见条码碳带质量问题有哪些影响?