Dubbo实现原理之基于SPI思想实现Dubbo内核
dubbo中SPI接口的定义如下:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface SPI {/*** 缺省扩展点名。*/String value() default "";}
dubbo默认的情况下,会依次从下面几个文件中读取扩展点。1.META-INF/dubbo/internal/ //dubbo内部实现的各种扩展都放在了这个目录了。2.META-INF/dubbo/。3.META-INF/services/。只有打了@SPI注解的接口类dubbo才会去查找扩展点实现。
我们以Protocol为例,Protocol接口上打了SPI注解,默认的扩展点名称为dubbo
@SPI("dubbo") public interface Protocol {int getDefaultPort();@Adaptive<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;@Adaptive<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;void destroy();}
dubbo中内置了各种协议,如DubboProtocol,HttpProtocol,HessianProtocol等等。Dubbo默认rpc模块默认protocol实现DubboProtocol,key为dubbo
ExtensionLoader类
1.ExtensionLoder.getExtensionLoader(Class<T> type)方法
每个定义的SPI接口,都会创建一个ExtensionLoader实例,存储在ConcurrentMap<Class<?>,ExtensionLoader<?>> EXTENSION_LOADERS这个map对象中
2.ExtensionLoader使用loadExtensionClasses方法读取扩展点中的实现类
loadExtensionClasses先读取SPI注解的value值,如果value有值,就把这个值作为默认扩展实现的key。然后再以此读取META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/下对应的文件。
3.我们以Protocal为例, loadFile逐行读取com.alibaba.dubbo.rpc.Protocol文件中的内容,每行内容以key/value形式存储。先判断实现类上是否打上了@Adaptive注解,如果打上了该注解,将此类作为Protocol协议的设配类缓存起来,读取下一行。如果实现类上没有打上@Adaptive注解,判断实现类是否存在参数为该接口的构造器,有的话作为包装类存储在该ExtensionLoader的Set<Class<?>> cachedWrapperClasses;集合中,这里用到了装饰器模式。如果该类既不是设配类,也不是wrapper对象,那就是扩展点的具体实现对象,查找实现类上是否打了@Activate注解,有缓存到变量cachedActivates的map中将实现类缓存到cachedClasses中,以便于使用时获取。如ProtocolFilterWrapper的实现如下:
public class ProtocolFilterWrapper implements Protocol {private final Protocol protocol;public ProtocolFilterWrapper(Protocol protocol) {if (protocol == null) {throw new IllegalArgumentException("protocol == null");}this.protocol = protocol;}.......... }
4.获取或则创建设配对象getAdaptiveExtension
如果cachedAdaptiveClass有值,说明有且仅有一个实现类打了@Adaptive, 实例化这个对象返回。如果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()方法
createAdaptiveExtensionClass方法源码如下:
private String createAdaptiveExtensionClassCode() {StringBuilder codeBuidler = new StringBuilder();Method[] methods = type.getMethods();boolean hasAdaptiveAnnotation = false;for (Method m : methods) {if (m.isAnnotationPresent(Adaptive.class)) {hasAdaptiveAnnotation = true;break;}}// 完全没有Adaptive方法,则不需要生成Adaptive类if (!hasAdaptiveAnnotation)throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");codeBuidler.append("package " + type.getPackage().getName() + ";");codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adaptive" + " implements " + type.getCanonicalName() + " {");for (Method method : methods) {Class<?> rt = method.getReturnType();Class<?>[] pts = method.getParameterTypes();Class<?>[] ets = method.getExceptionTypes();Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);StringBuilder code = new StringBuilder(512);if (adaptiveAnnotation == null) {code.append("throw new UnsupportedOperationException(\"method ").append(method.toString()).append(" of interface ").append(type.getName()).append(" is not adaptive method!\");");} else {int urlTypeIndex = -1;for (int i = 0; i < pts.length; ++i) {if (pts[i].equals(URL.class)) {urlTypeIndex = i;break;}}// 有类型为URL的参数if (urlTypeIndex != -1) {// Null Point checkString s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",urlTypeIndex);code.append(s);s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);code.append(s);}// 参数没有URL类型else {String attribMethod = null;// 找到参数的URL属性 LBL_PTS:for (int i = 0; i < pts.length; ++i) {Method[] ms = pts[i].getMethods();for (Method m : ms) {String name = m.getName();if ((name.startsWith("get") || name.length() > 3)&& Modifier.isPublic(m.getModifiers())&& !Modifier.isStatic(m.getModifiers())&& m.getParameterTypes().length == 0&& m.getReturnType() == URL.class) {urlTypeIndex = i;attribMethod = name;break LBL_PTS;}}}if (attribMethod == null) {throw new IllegalStateException("fail to create adative class for interface " + type.getName()+ ": not found url parameter or url attribute in parameters of method " + method.getName());}// Null point checkString s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",urlTypeIndex, pts[urlTypeIndex].getName());code.append(s);s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);code.append(s);s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);code.append(s);}String[] value = adaptiveAnnotation.value();// 没有设置Key,则使用“扩展点接口名的点分隔 作为Keyif (value.length == 0) {char[] charArray = type.getSimpleName().toCharArray();StringBuilder sb = new StringBuilder(128);for (int i = 0; i < charArray.length; i++) {if (Character.isUpperCase(charArray[i])) {if (i != 0) {sb.append(".");}sb.append(Character.toLowerCase(charArray[i]));} else {sb.append(charArray[i]);}}value = new String[]{sb.toString()};}boolean hasInvocation = false;for (int i = 0; i < pts.length; ++i) {if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {// Null Point checkString s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);code.append(s);s = String.format("\nString methodName = arg%d.getMethodName();", i);code.append(s);hasInvocation = true;break;}}String defaultExtName = cachedDefaultName;String getNameCode = null;for (int i = value.length - 1; i >= 0; --i) {if (i == value.length - 1) {if (null != defaultExtName) {if (!"protocol".equals(value[i]))if (hasInvocation)getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);elsegetNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);elsegetNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);} else {if (!"protocol".equals(value[i]))if (hasInvocation)getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);elsegetNameCode = String.format("url.getParameter(\"%s\")", value[i]);elsegetNameCode = "url.getProtocol()";}} else {if (!"protocol".equals(value[i]))if (hasInvocation)getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);elsegetNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);elsegetNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);}}code.append("\nString extName = ").append(getNameCode).append(";");// check extName == null?String s = String.format("\nif(extName == null) " +"throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",type.getName(), Arrays.toString(value));code.append(s);s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());code.append(s);// return statementif (!rt.equals(void.class)) {code.append("\nreturn ");}s = String.format("extension.%s(", method.getName());code.append(s);for (int i = 0; i < pts.length; i++) {if (i != 0)code.append(", ");code.append("arg").append(i);}code.append(");");}codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "(");for (int i = 0; i < pts.length; i++) {if (i > 0) {codeBuidler.append(", ");}codeBuidler.append(pts[i].getCanonicalName());codeBuidler.append(" ");codeBuidler.append("arg" + i);}codeBuidler.append(")");if (ets.length > 0) {codeBuidler.append(" throws ");for (int i = 0; i < ets.length; i++) {if (i > 0) {codeBuidler.append(", ");}codeBuidler.append(ets[i].getCanonicalName());}}codeBuidler.append(" {");codeBuidler.append(code.toString());codeBuidler.append("\n}");}codeBuidler.append("\n}");if (logger.isDebugEnabled()) {logger.debug(codeBuidler.toString());}return codeBuidler.toString();}
5. 通过createAdaptiveExtensionClassCode()生成的java源代码,要被java虚拟机加载执行必须得编译成字节码,dubbo提供两种方式去执行代码的编译1)利用JDK工具类编译2)利用javassit根据源代码生成字节码。
private Class<?> createAdaptiveExtensionClass() {String code = createAdaptiveExtensionClassCode();ClassLoader classLoader = findClassLoader();com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();return compiler.compile(code, classLoader);}
在此顺便介绍下@Adaptive注解打在实现类上跟打在接口方法上的区别:
如果有打在接口方法上,调ExtensionLoader.getAdaptiveExtension()获取设配类,会先通过前面的过程生成java的源代码,在通过编译器编译成class加载。但是Compiler的实现策略选择也是通过ExtensionLoader.getAdaptiveExtension(),如果也通过编译器编译成class文件那岂不是要死循环下去了吗?
ExtensionLoader.getAdaptiveExtension(),对于有实现类上去打了注解@Adaptive的dubbo spi扩展机制,它获取设配类不在通过前面过程生成设配类java源代码,而是在读取扩展文件的时候遇到实现类打了注解@Adaptive就把这个类作为设配类缓存在ExtensionLoader中,调用是直接返回
6. 自动Wrap上扩展点的Wrap类
Dubbo是如何自动的给扩展点wrap上装饰对象的呢?
在ExtensionLoader.loadFile加载扩展点配置文件的时候对扩展点类有接口类型为参数的构造器就是包转对象,缓存到集合中去。
在调ExtensionLoader的createExtension(name)根据扩展点key创建扩展的时候, 先实例化扩展点的实现, 在判断时候有此扩展时候有包装类缓存,有的话利用包转器增强这个扩展点实现的功能。具体实现如下:
private T createExtension(String name) {Class<?> clazz = getExtensionClasses().get(name);if (clazz == null) {throw findException(name);}try {T instance = (T) EXTENSION_INSTANCES.get(clazz);if (instance == null) {EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());instance = (T) EXTENSION_INSTANCES.get(clazz);}injectExtension(instance);Set<Class<?>> wrapperClasses = cachedWrapperClasses;if (wrapperClasses != null && wrapperClasses.size() > 0) {for (Class<?> wrapperClass : wrapperClasses) {instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));}}return instance;} catch (Throwable t) {throw new IllegalStateException("Extension instance(name: " + name + ", class: " +type + ") could not be instantiated: " + t.getMessage(), t);}}
7.ioc是spring的三大基础功能之一, dubbo的ExtensionLoader在加载扩展实现的时候内部实现了个简单的ioc机制来实现对扩展实现所依赖的参数的注入, dubbo对扩展实现中公有的set方法且入参个数为一个的方法,尝试从对象工厂ObjectFactory获取值注入到扩展点实现中去。
private T injectExtension(T instance) {try {if (objectFactory != null) {for (Method method : instance.getClass().getMethods()) {if (method.getName().startsWith("set")&& method.getParameterTypes().length == 1&& Modifier.isPublic(method.getModifiers())) {Class<?> pt = method.getParameterTypes()[0];try {String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";Object object = objectFactory.getExtension(pt, property);if (object != null) {method.invoke(instance, object);}} catch (Exception e) {logger.error("fail to inject via method " + method.getName()+ " of interface " + type.getName() + ": " + e.getMessage(), e);}}}}} catch (Exception e) {logger.error(e.getMessage(), e);}return instance;}
下面我们来看看ObjectFactory是如何根据类型和名字来获取对象的,ObjectFactory也是基于dubbo的spi扩展机制的。它跟Compiler接口一样设配类注解@Adaptive是打在类AdaptiveExtensionFactory上的不是通过javassist编译生成的。
AdaptiveExtensionFactory持有所有ExtensionFactory对象的集合,dubbo内部默认实现的对象工厂是SpiExtensionFactory和SpringExtensionFactory,他们经过TreeMap排好序的查找顺序是优先先从SpiExtensionFactory获取,如果返回空在从SpringExtensionFactory获取。
SpiExtensionFactory工厂获取要被注入的对象,就是要获取dubbo spi扩展的实现,所以传入的参数类型必须是接口类型并且接口上打上了@SPI注解,返回的是一个设配类对象
public <T> T getExtension(Class<T> type, String name) {if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);if (loader.getSupportedExtensions().size() > 0) {return loader.getAdaptiveExtension();}}return null;}
SpringExtensionFactory,Dubbo利用spring的扩展机制跟spring做了很好的融合。在发布或者去引用一个服务的时候,会把spring的容器添加到SpringExtensionFactory工厂集合中去, 当SpiExtensionFactory没有获取到对象的时候会遍历SpringExtensionFactory中的spring容器来获取要注入的对象
public <T> T getExtension(Class<T> type, String name) {for (ApplicationContext context : contexts) {if (context.containsBean(name)) {Object bean = context.getBean(name);if (type.isInstance(bean)) {return (T) bean;}}}return null;}
ExtensionLoader整体活动图如下:
转载于:https://www.cnblogs.com/senlinyang/p/8612883.html
Dubbo实现原理之基于SPI思想实现Dubbo内核相关推荐
- dubbo全局异常处理_基于spring aop的dubbo异常统一处理
dubbo统一异常处理,调用方只显示封装后的异常. 1.返回封装后的Exception 2.返回封装后的统一返回信息 import org.aspectj.lang.annotation.AfterT ...
- Dubbo的原理,面试常见问题
可能我们在平常工作中都会用到dubbo,但是好多同学只是知道应用但是却不一定知道他的原理,今天来说一说dubbo的原理,首先我们要知道dubbo到底是什么,都能提供些什么服务? 一.dubbo是什么? ...
- dubbo调用失败策略_面试官:dubbo负载均衡策略,集群容错策略,动态代理策略有哪些...
面试官心理分析 继续深问吧,这些都是用 dubbo 必须知道的一些东西,你得知道基本原理,知道序列化是什么协议,还得知道具体用 dubbo 的时候,如何负载均衡,如何高可用,如何动态代理. 说白了,就 ...
- 基于SPI的数据报过滤原理与实现
基于SPI的数据报过滤原理与实现 作者: TOo2y 一)个人防火墙技术概述 随着网络安全问题日益严重,广大用户对网络安全产品也越来越关注.防火墙作为一种网络安全工具,早已受到大家的青睐.在PC机上使 ...
- 分布式面试 - dubbo 的 spi 思想是什么?
分布式面试 - dubbo 的 spi 思想是什么? 面试题 dubbo 的 spi 思想是什么? 面试官心理分析 继续深入问呗,前面一些基础性的东西问完了,确定你应该都 ok,了解 dubbo 的一 ...
- 【Dubbo原理】(一)Dubbo的微内核架构及SPI机制
文章目录 Dubbo的微内核架构及SPI机制 1.什么是微内核架构 1.1.基本架构 1.2.设计关键点 2.Dubbo中的微内核架构 2.1.Dubbo的分层 2.2.Dubbo的插件模块 2.3. ...
- Dubbo核心源码之SPI扩展
本文来说下Dubbo核心源码之SPI扩展 文章目录 概述 Java中SPI机制详解 Dubbo SPI扩展 扩展功能介绍 扩展源码分析 ExtensionLoader初始化 配置文件扫描 扩展适配器 ...
- Dubbo 实现原理与源码解析系列 —— 精品合集
摘要: 原创出处 http://www.iocoder.cn/Dubbo/good-collection/ 「芋道源码」欢迎转载,保留摘要,谢谢! 1.[芋艿]精尽 Dubbo 原理与源码专栏 2.[ ...
- 面试时Dubbo原理记不住?来看看《Dubbo原理浅析——从RPC本质看Dubbo》
RPC的本质是什么?通俗地讲RPC就是要解决远程服务间的调用问题,也就是管理服务配置并提供便捷可靠高效的服务间调用. 我们来看看dubbo的定义:dubbo是一个分布式的服务框架,致力于提供高性能和透 ...
最新文章
- abb焊接机器人编程全解_焊接机器人的焊枪编程也是很重要的
- 异步复位和同步释放电路的详细解释
- concurrent(六)同步辅助器CyclicBarrier 源码分析
- Boost:align对齐的测试程序
- ActionScript3学习笔记2-包
- 从电商到软件市场,阿里双11战火蔓延
- 《软件需求分析(第二版)》第 16 章——需求链中的联系链 重点部分总结
- 字符串固定长度 易语言_易语言宽字符数据类型怎么设置
- Python CGI 编程 | 类FieldStorage的使用
- 哈夫曼编解码器C语言可运行
- 中职计算机英语教师教学总结,中职计算机教师教学工作总结 (3000字).doc
- centos7.2 使用rpm安装jdk8
- 图文并茂带你了解依存句法分析
- RTL8762DK UART(二)
- 当当网超级优惠券,别错过!(限时限量)
- MODBUS RTU协议
- Jvav-C++/真正的Jvav
- 精通WordPress设计与开发:第3章 本地开发WordPress
- 可变悬挂调节软硬_大众cc可变悬架软硬调节是什么意思
- 有一分数序列,2/1, 3/2, 5/3, 8/5, 13/8, 21/13,……,请编程求出这个数列的前20项之和。
热门文章
- 《C语言课程设计》一3.1 文件复制
- 部署Dashboard图形界面控制docker集群
- Spring @Async注解
- 微服务架构下,静态数据通用缓存机制!
- Spring Boot 把 Maven 干掉了,正式拥抱 Gradle!
- 后Hadoop时代的大数据技术思考:数据即服务
- Consul和ZooKeeper的区别
- 9.MYSQL:MyISAM表级锁(共享读锁 独占写锁)串行+并发
- 【Scratch】青少年蓝桥杯_每日一题_5.07_猜数字
- 【Python】青少年蓝桥杯_每日一题_10.11_小球反弹