JavaSPI机制

SPI是什么?

全成Service provider interface,中文意思是服务提供发现。它是JDK内置的一种服务提供发现机制

在微服务中也有服务发现,但是这两个并不是一个东西

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制

这就是典型的面向接口编程。

img

SPI实践(需要遵守SPI约定)

JDK中提供了一个工具类java.util.ServiceLoader,查找服务实现。

image-20200904220030863

可以看到他里面定义了一个常量PREFIX="META-INF/services/",它会根据这个前缀去查找jar包的META-INF/services/中的配置文件。配置文件中有接口的实现类全限定类名,可以根据类名进行加载实例化,就可以使用该服务了。

image-20200904215932866

可以看到

Service模块

  • 定义接口

image-20200904214537846

1
2
3
4
5
package cn.this52.service;

public interface PayService {
void pay();
}

AliPay实现模块

  • 定义实现类,引入Service jar,实现PayService接口

image-20200904214837968

1
2
3
4
5
6
7
8
9
10
package cn.this52.service.impl;

import cn.this52.service.PayService;

public class AliPay implements PayService {
@Override
public void pay() {
System.out.println("支付宝支付!");
}
}
1
2
3
4
5
<dependency>
<groupId>cn.this52</groupId>
<artifactId>pay_service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
  • 在resources目录下创建META-INF/services文件夹,创建一个以接口全限定类名命名的文件,文件内容为实现类全限定类名

image-20200904215438290

1
cn.this52.service.impl.AliPay

测试模块

image-20200904215655369

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.this52.main;

import cn.this52.service.PayService;

import java.util.ServiceLoader;

public class PayTest {
public static void main(String[] args) {
ServiceLoader<PayService> payServices = ServiceLoader.load(PayService.class);
for (PayService payService : payServices) {
System.out.println(payService);
payService.pay();
}
}
}

image-20200904220433657

成功调用到服务!

SPI约定

  • 当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;

  • 接口实现类所在的jar包放在主程序的classpath中;

  • 主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;

  • SPI的实现类必须携带一个不带参数的构造方法;

SPI具体应用

DriverManager

DriverManager是jdbc里管理和注册不同数据库driver的工具类。针对一个数据库,可能会存在着不同的数据库驱动实现。

Java定义了java.sql.Driver接口,并没有具体实现,都是由不同厂商来提供的

在JDBC4.0后连接数据库不需要再用Class.forName("com.mysql.jdbc.Driver")来加载驱动了,就是使用了Java的SPI扩展机制来实现的

mysql-connector-java 5.1.49jar中,META-INF/services目录下会有一个名字为java.sql.Driver的文件:

image-20200904222822698

文件内容,跟我们刚刚实践的是一样的

1
2
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

看下Mysql Driver实现

image-20200905091338632

可以看到它实现了java.sql.Driver接口,然后看下static静态块的DriverManage注册驱动

image-20200904222309829

可以看到static静态块中有个loadInitialDrivers()方法

image-20200904223607656

可以看到它使用的也是ServiceLoader类,遍历所有的jar下META-INF/services/中的以java.sql.Driver命名的文件里面的内容,并封装到一起,然后分割,通过Class.forName来加载类。就不需要我们来注册了。

Spring

评论