上一篇简单的介绍了 spi
的基本一些概念,但是其实 Dubbo
对jdk的spi进行了一些改进,具体改进了什么,来看看文档的描述
根据小学语文的阅读理解,不难概括出其实就是 提高了性能
和 增加了功能
.很多朋友都喜欢问,阅读源码不如从何下手,要准备些什么.如果只是粗略阅读源码,掌握大体思路,其实具备小学语文的阅读理解和看图写作业就差不多了(能看到本篇的均完全胜任这个条件,所以不要有任何恐惧心理).但是要领悟思想,对细节了如指掌,甚至写出更优秀的框架,那么就是四个字-> 终身学习
.(比如现在关注肥朝的公众号,一起交流讨论,终身学习即刻开启)
对于阅读源码,当然还是要有一些小技巧,俗话说得号,"技多不压身",我们先看一个段子
某肥遇到了一个bug,需要引入多线程,结果引入多线程后,竟然出现了两个bug
由此可见,多线程也确实是大家头疼的问题,所以后面我也会演示一些小技巧,比如
如何调试多线程代码(手把手实战,不讲理论)
如何查看代理对象源码(手把手实战,不讲理论)
那dubbo这个改良后的 spi
究竟怎么提高性能,又增加了什么功能,那就是本篇要讲的.
既然你对spi有一定了解,那么dubbo的spi和jdk的spi有区别吗?有的话,究竟有什么区别?
你说你看过Dubbo源码,那你简单说下,他在设计上有哪些细节让你觉得很巧妙?( 区分度高
)
dubbo的 拓展点机制
涉及到众多的知识点,也是dubbo中比较难的地方,和之前的集群容错有 Cluster
、 Directory
、 Router
、 LoadBalance
关键词一样,这个 拓展点机制
也有几个关键词, SPI
、 Adaptive
、 Activate
.这些会陆续讲解,最后总结.
提升性能,我们最容易想到的方式是什么?其实这个和初高中政治答题一样,有万能公式的,那就是"缓存".所以面试无论问你什么(适用于Android,iOS,Web前端,Java等等...),只要和提升性能有关的,往缓存** 方向
答肯定没错(当然按照"按点给分"的套路,往缓存方向答只是不至于让你拿0分,但是 仅仅
**答缓存肯定拿不到满分).所以如果与jdk的spi对比,那么可以有以下几个点
因为部分朋友反馈说喜欢贴代码的形式,所以讲解在注释中
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null)//拓展点类型非空判断 throw new IllegalArgumentException("Extension type == null"); if(!type.isInterface()) {//拓展点类型只能是接口 throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } if(!withExtensionAnnotation(type)) {//需要添加spi注解,否则抛异常 throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); } //从缓存EXTENSION_LOADERS中获取,如果不存在则新建后加入缓存 //对于每一个拓展,都会有且只有一个ExtensionLoader与其对应 ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; } private ExtensionLoader(Class<?> type) { this.type = type; //这里会存在递归调用,ExtensionFactory的objectFactory为null,其他的均为AdaptiveExtensionFactory //AdaptiveExtensionFactory的factories中有SpiExtensionFactory,SpringExtensionFactory //getAdaptiveExtension()这个是获取一个拓展装饰类对象.细节在下篇讲解 objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); } 复制代码
以下是缓存拓展点对象的
public T getExtension(String name) { if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); if ("true".equals(name)) { return getDefaultExtension(); } Holder<Object> holder = cachedInstances.get(name); if (holder == null) { cachedInstances.putIfAbsent(name, new Holder<Object>()); holder = cachedInstances.get(name); } Object instance = holder.get(); if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { instance = createExtension(name); holder.set(instance); } } } return (T) instance; } 复制代码
为什么这个要单独拿出来说呢?很多朋友容易产生大意心理,以为缓存嘛,无非就是判断一下是否存在,不存在则添加.dubbo也不过如此.我不看源码也懂.但是你如果稍加注意,就会发现它在细节方面做得很好.
在上一篇[从Dubbo内核聊聊双亲委派机制]中我就强调了,无论做什么,都要形成差异性.在Java中,最容易形成差异性的知识点,就是 JVM
和 并发包
.既然上一篇中我们提了 JVM
相关的内容(双亲委派机制),那么本篇我们就说说 并发包
的相关内容.我们仔细看上面的这段 double-checked locking
代码
//double-checked locking Object instance = holder.get(); if (instance == null) { synchronized (holder) { instance = holder.get(); if (instance == null) { instance = createExtension(name); holder.set(instance); } } } 复制代码
public class Holder<T> { private volatile T value; public void set(T value) { this.value = value; } public T get() { return value; } } 复制代码
这里为什么用到了 volatile
关键字呢?看源码更重要的是看到这些细节,魔鬼都藏在细节当中!本来肥朝想展开讲一下这个 volatile
秀一波操作的,但是无奈篇幅有限,那个我给你个关键词. doubleCheck Singleton 重排序
,那把这个对着浏览器一搜.然后把搜索到的第一页结果都看完,你就知道这段代码并不是你想的只是判空加个缓存这么简单.这也是我经常说,没有做过中间件开发,很难对并发包有深入了解的一个原因.
既然是对比 spi
区别,并且dubbo中有 @spi
这个注解,那我们顺着注解看看能有什么线索.
如果在15年有用过dubbo,那么就会留意到 @Extension
这个注解,但是后来因为含义广泛废弃,换成了 @SPI
.
@SPI("javassist") public interface Compiler { //省略... } 复制代码
public Class<?> compile(String code, ClassLoader classLoader) { Compiler compiler; ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); String name = DEFAULT_COMPILER; // copy reference if (name != null && name.length() > 0) { compiler = loader.getExtension(name); } else { compiler = loader.getDefaultExtension(); } return compiler.compile(code, classLoader); } 复制代码
//com.alibaba.dubbo.common.compiler.Compiler 文件配置如下 adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler 复制代码
我们从上面这两部分代码和配置文件就不难分析出两点(如果对spi不熟悉的请先把上一篇spi(一)看一遍,基础不牢地动山摇的情况下没法分析)
spi
要用for循环,然后if判断才能获取到指定的spi对象,dubbo用指定的key就可以获取 //返回指定名字的扩展 public T getExtension(String name){} 复制代码
spi
不支持默认值,dubbo增加了默认值的设计 //@SPI("javassist")代表默认的spi对象,比如Compiler默认使用的是javassist,可通过 ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); compiler = loader.getDefaultExtension(); //方式获取实现类,根据配置,即为 //com.alibaba.dubbo.common.compiler.support.JavassistCompiler 复制代码
增加的功能,就如文档所说的, spi
增加了 IoC
、 AOP
AOP
这是个老生常谈的话题了,谈到 AOP
大家最容易联想到 Spring
,甚至因为 AOP
常用在事务的场景,甚至就有不少人认为 AOP
就是事务.所以肥朝建议 初学者
学习 AOP
的路线大致如下:
// 这一步步演进的过程,才是最大的收获 装饰者设计模式->静态代理->JDK、cglib、Javassist优缺点对比->AOP源码 复制代码
##写在最后
肥朝 是一个专注于 原理、源码、开发技巧的技术公众号,号内原创专题式源码解析、真实场景源码原理实战(重点)。 扫描下面二维码 关注肥朝,让本该造火箭的你,不再拧螺丝!