前言
虽然标题是dubbo源码解析,但是本篇并不会出现dubbo的源码,本篇和之前的dubbo源码解析-简单原理、与spring融合一样,为dubbo源码解析专题的知识预热篇.
插播面试题
你是否了解spi,讲一讲什么是spi,为什么要使用spi?
对类加载机制了解吗,说一下什么是双亲委托模式,他有什么弊端,这个弊端有没有什么我们熟悉的案例,解决这个弊端的原理又是怎么样的?
spi的简单介绍
如果提到api相信大家都知道,spi的话,知道的人就相对少一些。
简单的说,api是给使用者使用的,spi是给拓展者使用的.一个好的开源框架,必须要留一些拓展点.让参与者尽量黑盒拓展,而不是白盒修改代码,否则分支,质量,合并,冲突都会很难管理.并且框架作者能做到的功能,拓展者也一定能做到.
如果从使用层面来说,就是运行时,动态给接口添加实现类.其实这有点像IoC的思想,将装配的控制权移到程序之外
如果从生活中的例子讲,就是比如浏览器插件,比如墙上的插头不够我们就接个排插,而不是伤筋动骨改插头(感觉不是很贴切,前期你暂且这么不规范的粗略理解)
再多的言语都是抽象的,那么我们用代码来简单实现一下spi
spi的简单实现
接口和具体实现类
public interface ISayName {
void say();
}
接口的一种实现
public class SayEnglishName implements ISayName{
@Override
public void say() {
System.out.println("Toby");
}
}
接口的另一实现
public class SayChineseName implements ISayName{
@Override
public void say() {
System.out.println("肥朝");
}
}
配置文件,需放置在META-INF/services/接口全限定名
com.toby.spi.impl.SayChineseName
com.toby.spi.impl.SayEnglishName
demo目录结构
测试结果如下
通过改变配置文件,我们就能动态的改变一个接口的实现类.
细心的小伙伴可能发现,比如我想新增一个实现类SayFranceNameImpl,这样的话光改配置文件也还是不行,还要预先包里面就有这个实现类才行啊.
这个先别急,后面会介绍javassist,也就是动态字节码技术.这样可以在运行时动态生成Java类,就不存在要预先把接口的实现类先在包里放好.更多内容,关注肥朝即可.
当然细心的小伙伴可能还发现了,这个我就算不用spi,我用spring的ioc也能通过配置文件动态的注入不同的实现类啊
比如dubbo的设计中,就不想强依赖Spring的IoC容器,但是自已造一个小的IoC容器,也觉得有点过度设计.另外dubbo是不需要依赖任何第三方库的,引用官方文档原话如下
理论上 Dubbo 可以只依赖 JDK,不依赖于任何三方库运行,只需配置使用 JDK 相关实现策略
敲黑板划重点
经常看到有人问两类问题
- java人这么多,是否饱和了?
- 为什么总是要面试造火箭,进去拧螺丝?
你可以问一下你同事,你知道什么是spi吗,如果他不知道的话,那你觉得他把上面的这个简单的例子实现要多久?如果从使用这个层面做区分的话,很难做到有效的区分.无论是做什么,要想在竞争中脱颖而出,就必须做到三个字.差异化.比如之前买喜茶要排很长的队,这其实也是一种差异化.
Java基础中比较容易产生差异化的两个区域就在于JVM和并发编程.如果只是停留在使用层面,那么关注肥朝的博客意义并不大,因此,本篇的spi还需要与ClassLoader结合.
学习JVM和并发编程买本书是必不可少的,以下内容参考了实战Java虚拟机.如果你看的是深入Java虚拟机也没关系,不要纠结于获取知识的渠道,没人在意你做的是五年高考三年模拟还是王后雄学案.
以下内容截取了该书中的部分核心内容,非常感谢作者的辛勤奉献(希望大家支持正版书籍).
从ClassLoader引出spi
ClassLoader的简单介绍
Class的装载大体上可以分为加载类、连接类和初始化三个阶段,在这三个阶段中,所有的Class都是由ClassLoader进行加载的,然后Java虚拟机负责连接、初始化等操作.也就是说,无法通过ClassLoader去改变类的连接和初始化行为.
Java虚拟机会创建三类ClassLoader,分别是
- BootStrap ClassLoader(启动类加载器)
- Extension ClassLoader(扩展类加载器)
- APP ClassLoader(应用类加载器,也称为系统类加载器)
此外,每个应用还可以自定义ClassLoader
ClassLoader的双亲委托模式
在ClassLoader的结构中,还有一个重要的字段parent,它也是一个ClassLoader的实例,这个字段表示的ClassLoader也称为这个ClassLoader的双亲.在类加载的过程中,可能会将某些加载类的请求交于自己的双亲处理.
如图,应用类加载器的双亲为扩展类加载器,扩展类加载器的双亲为启动类加载器.
系统中的ClassLoader在协同工作时,默认会使用双亲委托模式.即在类加载的时候,系统会判断当前类是否已经被加载,如果被加载,就会直接返回可用的类,否则就会尝试加载,在尝试加载时,会先请求双亲处理,如果双亲请求失败,则会自己加载.
双亲委托模式的弊端
判断类是否加载的时候,应用类加载器会顺着双亲路径往上判断,直到启动类加载器.但是启动类加载器不会往下询问,这个委托路线是单向的,即顶层的类加载器,无法访问底层的类加载器所加载的类,如图
启动类加载器中的类为系统的核心类,比如,在系统类中,提供了一个接口,并且该接口还提供了一个工厂方法用于创建该接口的实例,但是该接口的实现类在应用层中,接口和工厂方法在启动类加载器中,就会出现工厂方法无法创建由应用类加载器加载的应用实例问题.
拥有这样问题的组件有很多,比如JDBC、Xml parser等.JDBC本身是java连接数据库的一个标准,是进行数据库连接的抽象层,由java编写的一组类和接口组成,接口的实现由各个数据库厂商来完成
双亲委托模式的补充
在Java中,把核心类(rt.jar)中提供外部服务,可由应用层自行实现的接口,这种方式称为为spi.那我们看一下,在启动类加载器中,访问由应用类加载器实现spi接口的原理
Thread类中有两个方法
public ClassLoader getContextClassLoader()//获取线程中的上下文加载器
public void setContextClassLoader(ClassLoader cl)//设置线程中的上下文加载器
通过这两个方法,可以把一个ClassLoader置于一个线程的实例之中,使该ClassLoader成为一个相对共享的实例.这样即使是启动类加载器中的代码也可以通过这种方式访问应用类加载器中的类了.如下图
写在最后
本篇为2017年的最后一篇,提前祝大家元旦快乐.2018年,每周一篇dubbo源码解析还会继续更新.原因很简单,越是忙碌才越要抽出时间学习.我们要训练的是如何打逆风局,而不是怎么打顺风局.当然我非常不提倡大家像我一样熬夜(因明天要外出,所以不得不在今晚完成),不要忘了目前很火的电视剧虎啸龙吟中司马懿是怎么把诸葛亮熬死的.
有了本篇的知识铺垫,下一篇将从dubbo源码入手,看看dubbo是如何实现spi的.另外剧透一下,dubbo还对JDK标准的spi进行了一些改进.期待下周继续与你相遇.鉴于本人才疏学浅,不对的地方还望斧正。
Ref: