前言

上一篇简单的介绍了spi的基本一些概念,在末尾也提到了,dubbo对jdk的spi进行了一些改进,具体改进了什么,来看看文档的描述

JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不
存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

根据小学语文的阅读理解,不难概括出其实就是提高了性能和增加了功能.很多朋友都喜欢问,阅读源码不如从何下手,要准备些什么.如果只是粗略阅读源码,掌握大体思路,其实具备小学语文的阅读理解和看图写作业就差不多了(能看到本篇的均完全胜任这个条件,所以不要有任何恐惧心理).但是要领悟思想,对细节了如指掌,甚至写出更优秀的框架,那么就是四个字->终身学习.(比如现在关注肥朝,每周一篇,一起交流讨论,终身学习即刻开启)

对于阅读源码,当然还是要有一些小技巧,俗话说得号,"技多不压身",我们先看一个段子

某肥遇到了一个bug,需要引入多线程,结果引入多线程后,竟然出现了两个bug

由此可见,多线程也确实是大家头疼的问题,所以后面我也会演示一些小技巧,比如

  • 如何调试多线程代码(手把手实战,不讲理论)
  • 如何查看代理对象源码(手把手实战,不讲理论)

那dubbo这个改良后的spi究竟怎么提高性能,又增加了什么功能,那就是本篇要讲的.

插播面试题
既然你对spi有一定了解,那么dubbo的spi和jdk的spi有区别吗?有的话,究竟有什么区别?
概念铺垫
dubbo的拓展点机制涉及到众多的知识点,也是dubbo中比较难的地方,和之前的集群容错有Cluster、Directory、Router、LoadBalance关键词一样,这个拓展点机制也有几个关键词,SPI、Adaptive、Activate.这些会陆续讲解,最后总结.

直入主题
提升性能
提升性能,我们最容易想到的方式是什么?其实这个和初高中政治答题一样,有万能公式的,那就是"缓存".所以面试无论问你什么(适用于Android,iOS,Web前端,Java等等...),只要和提升性能有关的,往缓存方向答肯定没错(当然按照"按点给分"的套路,往缓存方向答只是不至于让你拿0分,但是仅仅答缓存肯定拿不到满分).所以如果与jdk的spi对比,那么可以有以下几个点

1.从"万能公式"角度分析,增加缓存

因为部分朋友反馈说喜欢贴代码的形式,所以讲解在注释中

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也不过如此.我不看源码也懂.但是你如果稍加注意,就会发现它在线程安全细节方面做得很好,比如

private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

现在知道为什么面试问完HashMap喜欢问ConcurrentHashMap了吧?这还不是个关键,我们继续看还有个很重要的类Holder,这个类用于保存一个值,并且给值添加volatile来保证线程的可见性.

public class Holder<T> {

    private volatile T value;

    public void set(T value) {
this.value = value;
} public T get() {
return value;
} }

看源码更重要的是看到这些细节,魔鬼都藏在细节当中!

2.从注解角度入手分析

既然是对比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(一)看一遍,基础不牢地动山摇的情况下没法分析)

JDK的spi要用for循环,然后if判断才能获取到指定的spi对象,dubbo用指定的key就可以获取

//返回指定名字的扩展
public T getExtension(String name){}

JDK的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动态代理->AOP

这里说的dubbo增加的IoC和AOP因为涉及到一个重要注解@Adaptive,后面会有专门一篇来详细讲,敬请期待.

写在最后

本篇以spi为引线,主要想表达的是分析问题的思路,新技术虽然层出不穷,但是只要掌握原理和分析问题的思路,自然能应对千变万化.鉴于肥朝才疏学浅,分析问题的思路仍有很多缺陷和不足,不足之处还望你不吝斧正.期待下周与你相遇,

Ref:

https://www.jianshu.com/p/e7446cdc7161

dubbo源码解析-spi(二)的更多相关文章

  1. dubbo源码解析-spi(4)

    前言 本篇是spi的第四篇,本篇讲解的是spi中增加的AOP,还是和上一篇一样,我们先从大家熟悉的spring引出AOP. AOP是老生常谈的话题了,思想都不会是一蹴而就的.比如架构设计从All in ...

  2. dubbo源码解析-spi(3)

    前言 在上一篇的末尾,我们提到了dubbo的spi中增加了IoC和AOP的功能.那么本篇就讲一下这个增加的IoC,spi部分预计会有四篇,因为这东西实在是太重要了.温故而知新,我们先来回顾一下,我们之 ...

  3. dubbo源码解析-spi(一)

    前言 虽然标题是dubbo源码解析,但是本篇并不会出现dubbo的源码,本篇和之前的dubbo源码解析-简单原理.与spring融合一样,为dubbo源码解析专题的知识预热篇. 插播面试题 你是否了解 ...

  4. dubbo源码解析五 --- 集群容错架构设计与原理分析

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...

  5. Dubbo 源码解析四 —— 负载均衡LoadBalance

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 Dubbo 入门之二 --- 项目结构解析 Dubbo 源码分析系列之三 -- 架构原 ...

  6. Cwinux源码解析(二)

    我在我的个人博客上发表了第二篇解析文章.欢迎各位读者批评指正. Cwinux源码解析(二)

  7. dubbo源码解析-zookeeper创建节点

    前言 在之前dubbo源码解析-本地暴露中的前言部分提到了两道高频的面试题,其中一道dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,那发布者和订阅者还能通信吗?在上周的dubbo源码 ...

  8. # Volley源码解析(二) 没有缓存的情况下直接走网络请求源码分析#

    Volley源码解析(二) 没有缓存的情况下直接走网络请求源码分析 Volley源码一共40多个类和接口.除去一些工具类的实现,核心代码只有20多个类.所以相对来说分析起来没有那么吃力.但是要想分析透 ...

  9. Spring事务源码解析(二)获取增强

    在上一篇文章@EnableTransactionManagement注解解析中,我们搭建了源码阅读的环境,以及解析了开启Spring事务功能的注解@EnableTransactionManagemen ...

随机推荐

  1. sqlite读写

    #coding=utf-8 import sqlite3 import os #创建数据库和游标 if os.path.exists(' test.db'): conn=sqlite3.connect ...

  2. MySQL数据库引擎详解

    作为Java程序员,MySQL数据库大家平时应该都没少使用吧,对MySQL数据库的引擎应该也有所了解,这篇文章就让我详细的说说MySQL数据库的Innodb和MyIASM两种引擎以及其索引结构.也来巩 ...

  3. 一个轻巧高效的多线程c++stream风格异步日志(二)

    目录 一个轻巧高效的多线程c++stream风格异步日志(二) 前言 LogFile类 AsyncLogging类 AsyncLogging实现 增加备用缓存 结语 一个轻巧高效的多线程c++stre ...

  4. SpringMVC框架03——数据绑定

    1.绑定基本数据类型 在Controller类中添加业务方法: /** * 绑定基本数据类型 */ @RequestMapping("/baseType") @ResponseBo ...

  5. Generator函数执行器-co函数库源码解析

    一.co函数是什么 co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行.短小精悍只有短短200余行,就可以免去手动编写G ...

  6. Java变量的默认值和初始化

    Java变量的默认值和初始化 学习自 <Thinking In Java> 技术小黑屋-为什么局部变量需要显式设置初始化值 变量的默认值 注意只有成员变量才有默认值,而局部变量必须要赋初值 ...

  7. Ubuntu 16.04 安装WPS

    1.下载安装包和缺少的字体 安装包下载[http://wps-community.org/download.html?vl=a21#download](这里下载的是最新的安装包) 缺少的字体下载[链接 ...

  8. hdu 4169 二分匹配最大独立集 ***

    题意:有水平N张牌,竖直M张牌,同一方向的牌不会相交.水平的和垂直的可能会相交,求最少踢出去几张牌使剩下的牌都不相交. 二分匹配 最小点覆盖=最大匹配. 链接:点我 坐标点作为匹配的端点 #inclu ...

  9. MultiByteToWideChar和WideCharToMultiByte

    CString UTF8ToGB2312(CString str) { int len; // UTF8转换成Unicode len = MultiByteToWideChar(CP_UTF8, 0, ...

  10. 使用 IntraWeb (13) - 基本控件之 TIWLabel、TIWLink、TIWURL、TIWURLWindow

    TIWLabel // TIWLink //内部链接 TIWURL //外部链接 TIWURLWindow //页内框架, 就是 <iframe></iframe> TIWLa ...