引入扩展点加载的原因
在大学软件工程这门课程上曾谈到升级维护是整个软件生命周期中最长的一个时间段, 软件的生命力长久与否与软件本身是否易维护、易扩展有直接的关系。所以开闭原则(对修改关闭,对扩展开放)对软件来说就显得非常的重要。
Dubbo的设计者为了实现dubbo的可扩展性,采用了一种微内核的设计思路。微内核架构将系统分成两个模块,一个是核心系统模块,另外一个是插件模块。核心系统负责的是和具体业务功能无关的通用功能,它的功能比较的稳定,不会因为业务功能的扩展而频繁的修改,对于Dubbo来说,Dubbo是一个RPC框架,所以Dubbo的核心系统就是暴露服务和调用服务的流程。而插件模块负责具体的业务功能的实现,插件系统是为核心系统服务的,比如在调用服务流程中,我们会缓存调用结果,而缓存既可以用本地缓存又可以用分布式缓存,插件模块可以根据业务功能的需要进行不断的扩展。微内核架构的设计思想就很好的实现了软件设计的开闭原则,将不变的(很少变化的)放在核心系统,而经常需要扩展的放在插件系统当中。
微内核的架构设计思想在spring中也有很好的体现,spring中的核心系统功能实现就是IOC和AOP,其中IOC负责bean控制反转和依赖注入流程,而AOP模块负责bean动态代理和切面的生成。对于插件模块来说,向外暴露的插件化设计接口如bean实例化前的接口BeanFactoryPostProcessor负责bean配置参数的修改,比如说数据库占位符的替换,实例化后的插件化接口如BeanPostProcessor接口InitializingBean负责bean实例化后的自定义扩展。
JDK的扩展点加载机制
上文提到了Dubbo的插件化设计思想,Dubbo插件化设计思想就是通过SPI机制实现的。在学习Dubbo的SPI机制前,我们先学习JDK的SPI机制,SPI机制是提供给框架和组件的开发者使用的,当框架开发者提供了一种接口实现之后,需要在classpath下的META-INF/services目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口具体的实现类。当其它的程序需要这个服务的时候,java的SPI工具ServiceLoader就可以查找META-INF/services目录路径下的配置文件,然后读取配置文件中的类名,加载类并实例化bean。下面来看一下数据库驱动的扩展点加载机制
数据库驱动加载
在java应用中,Mysql的驱动加载使用了SPI机制,通过该机制我们可以不再显示的使用如下的代码加载驱动
Class.forName("com.mysql.jdbc.Driver")
MySQL软件驱动包在META-INF/services目录下创建了java.sql.Driver文件
文件内容如下
DriverManager类在被虚拟机加载时会运行如下代码
其中loadInitialDrivers()就是DriverManager使用SPI机制加载mysql声明的驱动,loadInitialDrivers()核心代码如下
JDK SPI缺陷
jdk自带的扩展点加载机制存在以下几个问题,
- JDK自带的SPI会一次性实例化所有扩展点实现,如果扩展点不使用,那么会浪费资源
- 在扩展点加载失败的情况下,jdk扩展点加载机制无法提供扩展点加载失败的真正原因
- Jdk自带SPI机制不支持IOC和AOP的功能
Dubbo插件化系统
插件机制对于Dubbo的可扩展性有至关重要的作用,因为jdk SPI机制存在上文提到的几个缺陷,dubbo没有直接使用jdk的SPI机制,dubbo自己创建了一套SPI扩展机制ExtensionLoader。Dubbo是远程调用框架,Dubbo的SPI扩展机制是为远程调用服务的,远程调用框架最核心的功能是暴露服务和引用服务。但是不同的服务有不同的特点,比如状态性,有些服务是有状态的,那么服务提供者就希望消费者在调用时使用一致性hash算法的负载均衡逻辑。在dubbo中,服务提供者将服务的配置信息全部记录在向外暴露的url中。为了实现扩展性,我们希望服务调用者运行时能根据配置的url信息在负载均衡扩展点上选择一致性哈希算法。
如果服务提供者没有明确指明某个扩展点,那么我们应该提供默认扩展点配置。
于是我们知道Dubbo的扩展点加载机制需要满足如下两个需求
- 能根据URL配置加载指定的扩展点