谈到Dubbo老是避不开SPI思想,由于这个是Dubbo内核中很是重要的一部分,可是SPI是个很大的话题,本篇和以前的[dubbo源码解析-简单原理、与spring融合]同样,为dubbo源码解析专题的知识预热篇.咱们公司实际项目
就用到了Dubbo的SPI.后面会给你们分享,咱们实际项目中,是如何使用SPI,以及SPI后续咱们又是如何进优化的.java
你是否了解spi
,讲一讲什么是spi,为何要使用spi?面试
对类加载机制了解吗,说一下什么是双亲委托模式,他有什么弊端,这个弊端有没有什么咱们熟悉的案例,解决这个弊端的原理又是怎么样的?spring
若是提到api
相信你们都知道,spi
的话,知道的人就相对少一些.数据库
简单的说,api
是给使用者使用的,spi
是给拓展者使用的.一个好的开源框架,必需要留一些拓展点.让参与者尽可能黑盒拓展,而不是白盒修改代码,不然分支,质量,合并,冲突都会很难管理.而且框架做者能作到的功能,拓展者也必定能作到.编程
若是从使用层面来讲,就是运行时,动态给接口添加实现类.其实这有有点像IoC
的思想,将装配的控制权移到程序以外api
若是从生活中的例子讲,就是好比浏览器插件,好比墙上的插头不够咱们就接个排插,而不是伤筋动骨改插头(感受不是很贴切,前期你暂且这么不规范的粗略理解)浏览器
再多的言语都是抽象的,那么咱们用代码来简单实现一下spi
并发
接口和具体实现类框架
1public interface ISayName {
2 void say();
3}
1public class SayEnglishName implements ISayName{
2 @Override
3 public void say() {
4 System.out.println("Toby");
5 }
6}
1public class SayChineseName implements ISayName{
2 @Override
3 public void say() {
4 System.out.println("肥朝");
5 }
6}
配置文件,需放置在META-INF/services/接口全限定名
ide
1com.toby.spi.impl.SayChineseName
2com.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虚拟机
也不要紧,不要纠结于获取知识的渠道,没人在乎你作的是五年高考三年模拟仍是王后雄学案.
如下内容截取了该书中的部分核心内容,很是感谢做者的辛勤奉献(但愿你们支持正版书籍).
Class
的装载
大致上能够分为加载类
、链接类
和初始化
三个阶段,在这三个阶段中,全部的Class
都是由ClassLoader
进行加载的,而后Java虚拟机负责链接、初始化等操做.也就是说,没法经过ClassLoader
去改变类的链接和初始化行为.
Java虚拟机会建立三类ClassLoader
,分别是
BootStrap ClassLoader(启动类加载器)
Extension ClassLoader(扩展类加载器)
APP ClassLoader(应用类加载器,也称为系统类加载器)
此外,每一个应用还能够自定义ClassLoader
在ClassLoader
的结构中,还有一个重要的字段parent
,它也是一个ClassLoader
的实例,这个字段字段表示的ClassLoader
也成为这个ClassLoader
的双亲.在类加载的过程当中,可能会将某些请求交于本身的双亲处理.
如图,应用类加载器
的双亲为扩展类加载器
,扩展类加载器
的双亲为启动类加载器
.
系统中的ClassLoader
在协同工做时,默认会使用双亲委托模式
.即在类加载的时候,系统会判断当前类是否已经被加载,若是被加载,就会直接返回可用的类,不然就会尝试加载,在尝试加载时,会先请求双亲处理,若是双亲请求失败,则会本身加载.
判断类是否加载的时候,应用类加载器会顺着双亲路径往上判断,直到启动类加载器.可是启动类加载器不会往下询问,这个委托路线是单向的,即顶层的类加载器,没法访问底层的类加载器所加载的类,如图
启动类加载器中的类为系统的核心类,好比,在系统类中,提供了一个接口,而且该接口还提供了一个工厂方法用于建立该接口的实例,可是该接口的实现类在应用层中,接口和工厂方法在启动类加载器中,就会出现工厂方法没法建立由应用类加载器加载的应用实例问题.
拥有这样问题的组件有不少,好比JDBC
、Xml parser
等.JDBC自己是java链接数据库的一个标准,是进行数据库链接的抽象层,由java编写的一组类和接口组成,接口的实现由各个数据库厂商来完成
在Java中,把核心类(rt.jar)中提供外部服务,可由应用层自行实现的接口,这种方式成为spi
.那咱们看一下,在启动类加载器
中,访问由应用类加载器
实现spi接口的原理
Thread
类中有两个方法
1public ClassLoader getContextClassLoader()//获取线程中的上下文加载器
2public void setContextClassLoader(ClassLoader cl)//设置线程中的上下文加载器
3
经过这两个方法,能够把一个ClassLoader
置于一个线程的实例之中,使该ClassLoader
成为一个相对共享的实例.这样即便是启动类加载器中的代码也能够经过这种方式访问应用类加载器中的类了.以下图
关注肥朝公众号,后续还会有更多奇巧淫技,真实企业场景源码级实战和你们分享.让"原理"再也不只是面试装逼.也欢迎你们留言一块儿交流精进。