Dubbo 2.7.3源码分析——Dubbo SPI(二)
老板,救救孩子?关注一下吧!
作者 肥又君
前情回顾
在前面介绍了Dubbo SPI 中 ExtensionLoader 的基础使用和原理,本篇重点介绍 Dubbo SPI 对 @Adaptive 注解的处理。
@Adaptive注解简介
@Adaptive 注解是什么东西?
通过名字就能看出,它是用来标注适配器的。Dubbo SPI能根据配置选择实现类了,那还要适配器干啥呢?
简单点说,就是动态选择实现,META-INF 目录下面的配置毕竟还是静态的,要是能根据参数选择实现,那就爽歪歪了。
@Adaptive 注解可以标注在类上,也可以标注在方法上。这里直接先摆出 @Adaptive 注解的功能:
当 @Adaptive 注解直接标记适配器类上的时候,优先级最高,Dubbo直接使用该类作为接口实现。
当 @Adaptive 注解标记在方法上时,Dubbo 会解析该方法上的URL类型参数,确定该方法使用的具体实现类。
对于2,有几点说明,一个是这里的URL是org.apache.dubbo.common.URL,先把它理解成一个KV就行,【整个Dubbo系列中,只要没有特殊说明,URL都是指的 org.apache.dubbo.common.URL ,千万别和java.net.URL混了,混了的都是傻】。
另一个要想动态选择实现,就必须要有代理,代理类是跑不掉的,既然我们啥都没干,那肯定是 Dubbo 帮我们做了,后面的代码会详细说。
@Adaptive 注解示例
这个示例重点展示上述第2点的效果,涉及一个接口—— Car,两个实现类—— BMW 和 Audi,还有一个 SPI 配置文件,当然还有一个 Main 方法。
首先,我们先定义 Car 接口:
@SPI("BMW") // @SPI注解表示该接口是个扩展点
public interface Car { // 看到第一个URL参数了吗?@Adaptive会告诉Dubbo SPI // 生成的代理类,解析这个URL参数,根据解析到的type参数 // 确定start()方法使用哪个实现类。如果没有type参数, // 则尝试通过model参数确定实现类 @Adaptive({"type", "model"}) void start(URL url, String who); @Adaptive({"type", "model"}) void stop(URL url, String who);
}
接下来是两个平淡无奇,但是想坐在里面哭的实现类—— BMW 和 Audi,醒醒,看代码吧:
public class BMW implements Car{ @Override public void start(URL url, String who) { System.out.println(who+" is starting the BMW ..."); } @Override public void stop(URL url, String who) { System.out.println(who+" is stoping the BMW ..."); }
} public class Audi implements Car{ @Override public void start(URL url, String who) { System.out.println(who+" is starting the Audi ..."); } @Override public void stop(URL url, String who) { System.out.println(who+" is stoping the Audi ..."); }
}
接下来是 org.apache.dubbo.common.extension.test.Car 配置文件,注意哦,这个文件是放到 resources/META-INF/dubbo/internal 目录下的,为什么放到这里?翻翻关注公众号翻翻上一篇对ExtensionLoader的分析吧。
BMW=org.apache.dubbo.common.extension.test.BMW
Audi=org.apache.dubbo.common.extension.test.Audi
最后来看Main 方法:
public static void main(String[] args) throws Exception { // 这里调用的是getAdaptiveExtension()方法,而不是 // 上一篇介绍的getExtension()方法哦! Car car = ExtensionLoader .getExtensionLoader(Car.class) .getAdaptiveExtension(); // 注意这个URL,没有type参数也没有model参数,Dubbo会根据Car接口@SPI注解中 // 指定的默认值选择实现类 URL url = URL.valueOf("test://mycar"); car.start(url, "黄宏斯坦森"); // 输出:黄宏斯坦森 is starting the BMW ... // 优先根据URL中的type参数选择实现 URL startUrl = URL.valueOf("test://mycar?type=BMW&model=Audi"); car.start(startUrl, "肥又君"); // 输出:肥又君 is starting the BMW ... // 没有type参数根据URL中的model选择实现 URL stopUrl = URL.valueOf("test://mycar?model=Audi"); car.stop(stopUrl, "百里"); // 百里 is stoping the Audi ...
}
@Adaptive 注解实现原理
前面说了,一个Car对象会根据URL参数切换具体的实现主要是因为Dubbo SPI会生成一个Adaptive的代理类。下面就来分析 ExtensionLoader.getAdaptiveExtension() 方法的如何处理@Adaptive注解以及生成Adaptive代理的。
先来看 ExtensionLoader.getAdaptiveExtension() 方法的实现:
// cachedAdaptiveInstance又是一个Holder,用来缓存Adaptive代理类
private final Holder<Object> cachedAdaptiveInstance = new Holder<>(); public T getAdaptiveExtension() { // 每个SPI接口最多就只能有一个Adaptive代理对象,这里先查缓存 Object instance = cachedAdaptiveInstance.get(); // double-check处理并发问题 if (instance == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { // 查找并创建Adaptive代理类实例 instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); // 设置缓存 } } } return (T) instance;
}
下面来看 createAdaptiveExtension() 方法的核心流程:
private volatile Class<?> cachedAdaptiveClass = null; private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { ... ... // 如果发现一个实现类被@Adaptive注解标记了,则记录到cachedAdaptiveClass字段, // 这个字段后面有用 if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz); } else ... ...
}
private Class<?> createAdaptiveExtensionClass() { // 生成Adaptive代理类,返回的code就是生成的Java代码 String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); // 获取ClassLoader ClassLoader classLoader = findClassLoader(); // 通过SPI方式获取Compiler实现类 org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class) .getAdaptiveExtension(); // 编译并加载上面生成的Java代码 return compiler.compile(code, classLoader);
}
简单看看上面示例生成的 Adaptive 代理 —— Car$Adaptive:
public class Car$Adaptive implements org.apache.dubbo.common.extension.test.Car { public void start(org.apache.dubbo.common.URL arg0, java.lang.String arg1) { if (arg0 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg0; // 尝试先尝试获取type,然后尝试获取model,最后再用"BMW"默认值 String extName = url.getParameter("type", url.getParameter("model", "BMW")); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.test.Car) name from url (" + url.toString() + ") use keys([type, model])"); // 最后还是调用上一篇介绍的getExtension()方法 org.apache.dubbo.common.extension.test.Car extension = (org.apache.dubbo.common.extension.test.Car) ExtensionLoader .getExtensionLoader(org.apache.dubbo.common.extension.test.Car.class) .getExtension(extName); extension.start(arg0, arg1); } public void stop(org.apache.dubbo.common.URL arg0, java.lang.String arg1) { ... // 生成的stop()方法与start()方法类似,不再粘贴 }
}
最后,看一下 AdaptiveClassCodeGenerator.generate() 方法是如何生成 Adaptive代理类的:
public String generate() { StringBuilder code = new StringBuilder(); code.append(generatePackageInfo()); // 生成package部分 code.append(generateImports()); // 生成import部分 code.append(generateClassDeclaration()); // 生成类签名 Method[] methods = type.getMethods(); // 根据SPI接口生成相应的方法代码 for (Method method : methods) { code.append(generateMethod(method)); } code.append("}"); return code.toString();
}
@Activate注解的事情,下次再说?
Dubbo 2.7.3源码分析——Dubbo SPI(二)相关推荐
- 源码分析Dubbo服务注册与发现机制RegistryDirectory)
RegistryDirectory,基于注册中心的服务发现,本文将重点探讨Dubbo是如何实现服务的自动注册与发现.从上篇文章,得知在消息消费者在创建服务调用器(Invoker)[消费者在初始时]时需 ...
- 源码分析Dubbo监控中心实现原理
Dubbo监控的实现基本原理就是在服务调用时收集服务调用并发度.服务响应时间,然后以一定频率向监控中心汇报统计数据. 1.源码分析MonitorFilter过滤器 过滤器作用 监控过 ...
- Dubbo系列(二)源码分析之SPI机制
Dubbo系列(二)源码分析之SPI机制 在阅读Dubbo源码时,常常看到 ExtensionLoader.getExtensionLoader(*.class).getAdaptiveExtensi ...
- 源码分析Dubbo系列文章
本系列文章主要针对Dubbo2.6.2(dubbox2.8.4)版本,从源码的角度分析Dubbo内部的实现细节,加深对Dubbo的各配置参数底层实现原理的理解,更好的指导Dubbo实践,其目录如 ...
- idea 线程内存_Java线程池系列之-Java线程池底层源码分析系列(二)
课程简介: 课程目标:通过本课程学习,深入理解Java线程池,提升自身技术能力与价值. 适用人群:具有Java多线程基础的人群,希望深入理解线程池底层原理的人群. 课程概述:多线程的异步执行方式,虽然 ...
- Spring Boot Dubbo 应用启停源码分析
作者:张乎兴 来源:Dubbo官方博客 背景介绍 Dubbo Spring Boot 工程致力于简化 Dubbo RPC 框架在Spring Boot应用场景的开发.同时也整合了 Spring Boo ...
- 源码分析Dubbo服务消费端启动流程
通过前面文章详解,我们知道Dubbo服务消费者标签dubbo:reference最终会在Spring容器中创建一个对应的ReferenceBean实例,而ReferenceBean实现了Spring生 ...
- 源码分析Dubbo服务提供者启动流程-下篇
本文继续上文Dubbo服务提供者启动流程,在上篇文章中详细梳理了从dubbo spring文件开始,Dubbo是如何加载配置文件,服务提供者dubbo:service标签服务暴露全流程,本节重点关注R ...
- 源码分析Dubbo服务提供者启动流程-上篇
本节将详细分析Dubbo服务提供者的启动流程,请带着如下几个疑问进行本节的阅读,因为这几个问题将是接下来几篇文章分析的重点内容. 1.什么时候建立与注册中心的连接. 2.服务提供者什么时候向注册中 ...
最新文章
- 2021-2027年中国医疗旅游业投资分析及前景预测报告
- 批号数量调整单中批次数量和库存数量不一致
- 十种创业病 你得了几个?
- 堆栈——Windows核心编程学习手札之十八
- php页头滚动文字公告,jQuery公告栏文字滚动插件
- VS2017 按ctrl+f5执行程序窗口依然一闪而过的问题(图文)
- [SQL Server]树形结构的创建
- Could not resolve the package 'english_words' in 'package:english_words/english_words.dart'
- 搭建开发环境以及STM32固件库移植
- 【云和恩墨大讲堂】视频课程震撼来袭-SQL优化精选案例
- maccms10自动播放下一集
- 安装安全防护软件有助于保护计算机不受侵害,安装安全防护软件有助于保护计算机不受病毒侵害。...
- c++课程设计图书馆管理系统总结
- python 布莱克舒尔斯_布莱克—舒尔斯期权定价模型
- FMI人工智能与大数据线下沙龙第869期北京场圆满落幕
- 计算机学院刘岗,刘岗-中国科学院大学-UCAS
- OpenGL ES之GLSL实现多种“马赛克滤镜”效果
- PPT、word破解加密文档
- 西瓜皮——被丢掉的真金白银,夏天的健康守护神
- “善弈者通盘无妙手”:网易的To B棋局
热门文章
- 3DMax烘焙CompleteMap贴图导入进Unity3D
- springboot 集成 swagger 和knife4j
- 清华大学 陈鑫 计算机,我院学子在第七届海峡两岸信息服务创新大赛中获佳绩...
- 数据库事务和一致性处理
- 程序中有游离的...
- 学用计算机图片,电脑怎么截图?1分钟教你学会用电脑自带截图工具瞬间截图...
- CMFCStatusBar 状态栏字体颜色为灰色
- MFC 设置static(标签)控件背景透明
- 判断魔方阵c语言程序设计_C语言编程,输出魔方阵
- autocad锐龙英特尔_到底差多少?十代标压酷睿对比锐龙4000H