Java中SPI机制介绍
SPI机制简介
SPI的全名为Service Provider Interface。大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的。在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java spi机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
总结的来说,SPI是一种旨在由插件/模块实现或扩展的API。
让我们以一个例子来理解。
示例
假设我们正在设计应用程序框架F。我们希望F是可扩展的。我们将创建对外界可见的API接口/类,通常称为服务来描述F的作用。一个特定的应用程序A将调用该API来实现一个目标。F的相同接口/类(SPI)的集合通过插件和模块被视为扩展点。这些插件/模块通常称为服务提供商。他们提供具体的实现或扩展框架的这些接口/类来提供功能。
创建四个工程
- FrameworkF: 框架工程,内含main入口,对服务进行调用,依赖于下面三个工程
- ApiX: 服务接口工程,定义服务的接口
- PluginY: 服务提供商Y,实现了ApiX中的服务
- PluginZ:服务提供商Z,实现了ApiX中的服务
在FrameworkF的main函数中有如下调用
1 |
|
在ApiX的定义服务
1 |
|
在插件Y中,实现接口。
并定义了SPI接口文件,就是PluginY\src\main\resources\META-INF\services\com.example.apix.MyService
同理插件Z中,也一样的实现了。
在执行在FrameworkF的main函数时,可能会有如下输出:
1 |
|
main中找到了pluginY和pluginX的实现类,并进行了调用。
具体的代码放在了github上,见这里。
SPI具体约定
当服务的提供者(如上面的pluginY,pluginZ),提供了服务接口的一种实现之后(如MyServicePluginYImpl,MyServicePluginZImpl),在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件(如com.example.apix.MyService)。该文件里就是实现该服务接口的具体实现类(如com.example.pluginy.MyServicePluginYImpl)。而当外部程序装配这个模块的时候(FrameworkF 加上PluginY的依赖),就能通过该jar包(PluginY和PluginZ的jar)META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。
jdk提供服务实现查找的一个工具类:java.util.ServiceLoader
应用场景
jdbc
在jdbc的jar包中有文件META-INF/services/java.sql.Driver
有文件内容com.mysql.jdbc.Driver
指定实现类的方式来暴露驱动提供者。
tomcat
在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等,servlet规范中通过ServletContainerInitializer实现此功能。就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer
的文件,文件内容指定具体的ServletContainerInitializer实现类,那么,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作。
common-logging
有些文章是说common-logging中有,但我没有找到。