目录
1.什么是SPI
SPI:Service Provider Interface,是Java提供的一套被第三方实现或者扩展的接口,用来启用框架扩展和替换组件。
2.为什么要使用SPI思想
为了降低耦合性、提高系统可扩展性。一般来说对于未知的实现或者对扩展开放的系统,通常会把一些逻辑抽成各个模块,这些模块往往有很多不同的实现方案,比如对接不同第三方日志系统、对接不同数据库驱动、编程软件需要加载一些用户开发的插件等。总结来说,SPI思想适用于调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略。
3.SPI 调用机制
4.SPI 具体应用案例
4.1 JDK
非常简单的四个类
Leads:线索接口
public interface Leads {
/**
* 接收线索分发器下发的线索
* @return 线索名称
*/
String receiveLeads();
}
LeadsManager:线索管理类
public class LeadsManager {
public static synchronized void initLeadsManager() {
loadInitialParsers();
}
private static void loadInitialParsers() {
System.out.println("LeadsManager initialized");
ServiceLoader<Leads> loadedParsers = ServiceLoader.load(Leads.class);
for (Leads lead : loadedParsers) {
System.out.println(lead.receiveLeads());
}
}
}
BmwLeads:宝马线索实现类
public class BmwLeads implements Leads {
@Override
public String receiveLeads() {
return "宝马线索下发成功";
}
}
BenzLeads:奔驰线索实现类
public class BenzLeads implements Leads {
@Override
public String receiveLeads() {
return "奔驰线索下发成功";
}
}
APP:测试类
public class App {
public static void main(String[] args) {
LeadsManager.initLeadsManager();
}
}
另外还有两个重要的配置文件:
注: 1.需要在resources目录下新建META-INF/services目录
2.在resources/META-INF/services新建一个文件,命名与需扩展接口的全限定名一致
3.在这个文件中写入接口的实现类的全限定名
4.接口实现类所在的jar包放在主程序的classpath中
5.主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM中
6.SPI的实现类必须携带一个不带参数的构造方法
4.2 DriverManager
在加载MySql驱动的时,我们都调用了这样一行函数Class.forName("com.mysql.jdbc.Driver"),但为什么调用这样一行代码就可以加载mysql驱动了呢?其实这里也运用了SPI的思想。SDK提供了这样一个接口:java.sql.Driver
而不同的数据库厂商又对这个接口进行了不同的实现,比如mysql的Driver:
继续跟进JDK提供的DriverManager类:
见到了我们熟悉的代码ServiceLoader.load(XXX.class);同时在loadInitialDrivers()方法中也有 Class.forName()。
最后去看下mysql的驱动包是否满足java SPI加载条件:
4.3 Spring 自定义标签
扩展Spring自定义标签配置需要以下几个步骤
- 创建需要扩展的组件
- 定义XSD文件描述组件内容
- 创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义
- 创建Handler文件,扩展字NamespaceHandlerSupport,目的是将组件注册到Spring容器
- 编写Spring.handlers和Spring.schemas文件
Spring为我们定义好了创建标签的流程,而无需了解我们具体的实现,只需要我们通过Spring提供的加载方式把插件加载进来,体现了spi的思想。