阅读优秀的开源框架是程序员进步的一种捷径。但愿本身能坚持下去。有个好头也有个好的结尾。这部分先讲讲dubbo最核心的设计思想。微内核插件式开发。这种思想贯穿了他的整个项目。在讲微内核以前,可能先要讲讲java的SPI。其实dubbo就是在SPI设计思想基础上进行了升级。java
SPI全称Service Provider Interface。字面上理解是面向接口提供服务。他很好的诠释了面向接口编程,以及OCP原则等。你们都知道,咱们在模块化编程的时候,每每模块之间是基于接口编程的。例如A模块须要使用一种服务,但具体实现交由第三方实现。那么A会定义出一个接口让B去实现,可是A在实例化这个接口的实现时,每每须要硬编码B的一种实现。这就会致使B若是换了实现方式,又或者A想使用C的实现方式,那么须要修改A的实例化代码去支持实现的修改切换。在OCP原则里,这种修改是须要避免的。因此咱们须要一种实现发现机制。在实例化时不该该硬编码一种实现,而是为接口提供一种实现发现机制,而后把实例化这部分功能移到A模块以外。这种思想跟IOC很像。咱们在用Spring的时候,就是把对象的装配交给了Spring处理,这样层于层之间才正真达到了面向接口编程。程序员
假如A模块设计定义了一个接口。当B实现了这个接口以后,须要在B的jar包的META-INF/services/目录里添加一个文件,这个文件名字就是A定义的这个接口的全路径名,内容就是B实现接口的类的全路径名。此时,若是A须要使用B的实现,引入B的jar包。而后A可使用java.util.ServiceLoader去发现这个实现,并实例化后返回给A。这样A在使用这个实现时,只是用定义好的接口引用了该实现,不用去硬编码这个实现。若是有一天须要替换B的实现,咱们只要引入别的第三方jar,根本不用修改A的代码。sql
例如A模块定义了一个接口,以下:apache
public interface DemoApi { String sayHello(String name); }
而后B实现这个接口,以下:编程
public class DemoApiImpl1 implements DemoApi { public String sayHello(String name) { return name + "你好,我是DemoApiImpl1实现"; } }
假如咱们不用SPI设计思想去设计程序,那么我在A模块中要须要下面这样去使用DemoApi接口,这个问题就来了,我在设计A模块的时候,须要去依赖B模块,并且哪天我想替换B模块的实现,我还得动A模块的代码,从新new一个别的实现。其实A模块只须要使用DemoApi接口的服务,至于这个接口的具体实现根本不须要关心,更不须要事先依赖某种实现。框架
public static void main(String args[]) { DemoApi demoApi = new DemoApiImpl1(); demoApi.sayHello("xuanner"); }
那么,咱们来改进一下,使用SPI来编码使用DemoApi接口的服务,以下:ide
public static void main(String args[]) { ServiceLoader<DemoApi> serviceLoader = ServiceLoader.load(DemoApi.class); Iterator<DemoApi> iterator = serviceLoader.iterator(); System.out.println("能够遍历多种可能存在的实现"); while (iterator.hasNext()) { DemoApi demoApi = iterator.next(); demoApi.sayHello("xuanner") } }
固然,B模块在实现以后须要在他的JAR包的META-INF/services/目录下放一个配置文件,看以下图:模块化
里面的内容是实现类的全路径,以下:编码
com.xuan.spidemo.impl1.DemoApiImpl1
当A模块须要B的实现的时候,只要进入B的JAR,ServiceLoader就会在JAR下找到对应接口的实现,而后实例化返回提供给A使用,若是有一天咱们须要替换用C去实现,那么只要C实现代码后,一样在他的JAR下放上面的配置文件便可。spa
其中java.sql.Driver的实现就就是基于使用了SPI。还有commons-logging日志系统。也是有基于SPI实现的,我只简单的摘录了几个相关的片断,只不过他好像不是使用了ServiceLoader类去寻找实现,而是本身实现加载文件的。
protected static final String SERVICE_ID = "META-INF/services/org.apache.commons.logging.LogFactory";
if (factory == null) { if (isDiagnosticsEnabled()) { logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID + "] to define the LogFactory subclass to use..."); } try { final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID); if( is != null ) { // This code is needed by EBCDIC and other strange systems. // It's a fix for bugs reported in xerces BufferedReader rd; try { rd = new BufferedReader(new InputStreamReader(is, "UTF-8")); } catch (java.io.UnsupportedEncodingException e) { rd = new BufferedReader(new InputStreamReader(is)); }
其实SPI设计思想也很简单,接地气一点的解释就是,我设计了一个接口,而后本身不实现,当须要使用这个接口的实现时,就用ServiceLoader类去各个依赖的第三方包中扫描,只要扫描到有实现的类,就进行实例化提供服务。对我来讲,实现是彻底透明的,我只根据接口的方法来编程,正真作到了面向接口编程。因此当咱们须要换一种实现时,只要替换一下第三方依赖JAR就好了,个人其余代码就不用动了。这种方式用来给第三方本身扩展是否是很赞。对的。