上一篇简单的介绍了spi
的基本一些概念,可是其实Dubbo
对jdk的spi进行了一些改进,具体改进了什么,来看看文档的描述前端
- JDK 标准的 SPI 会一次性实例化扩展点全部实现,若是有扩展实现初始化很耗时,但若是没用上也加载,会很浪费资源。
- 若是扩展点加载失败,连扩展点的名称都拿不到了。好比:JDK 标准的 ScriptEngine,经过 getName() 获取脚本类型的名称,但若是 RubyScriptEngine 由于所依赖的 jruby.jar 不存在,致使 RubyScriptEngine 类加载失败,这个失败缘由被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的缘由。
- 增长了对扩展点 IoC 和 AOP 的支持,一个扩展点能够直接 setter 注入其它扩展点。
根据小学语文的阅读理解,不难归纳出其实就是提升了性能
和增长了功能
.不少朋友都喜欢问,阅读源码不如从何下手,要准备些什么.若是只是粗略阅读源码,掌握大致思路,其实具有小学语文的阅读理解和看图写做业就差很少了(能看到本篇的均彻底胜任这个条件,因此不要有任何恐惧心理).可是要领悟思想,对细节了如指掌,甚至写出更优秀的框架,那么就是四个字->终身学习
.(好比如今关注肥朝的公众号,一块儿交流讨论,终身学习即刻开启)java
对于阅读源码,固然仍是要有一些小技巧,俗话说得号,"技多不压身",咱们先看一个段子面试
某肥遇到了一个bug,须要引入多线程,结果引入多线程后,居然出现了两个bug设计模式
因而可知,多线程也确实是你们头疼的问题,因此后面我也会演示一些小技巧,好比浏览器
如何调试多线程代码(手把手实战,不讲理论)
缓存
如何查看代理对象源码(手把手实战,不讲理论)
ruby
那dubbo这个改良后的spi
究竟怎么提升性能,又增长了什么功能,那就是本篇要讲的.bash
既然你对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源码
复制代码
肥朝 是一个专一于 原理、源码、开发技巧的技术公众号,号内原创专题式源码解析、真实场景源码原理实战(重点)。扫描下面二维码关注肥朝,让本该造火箭的你,再也不拧螺丝!