SPI全称Service Provider Interface,是Java提供的一种让第三方实现或扩展的API。
java平台中很多功能都是以这种方式提供接口给开发者调用的,最典型的如:JDBC,JDNI,JCE(Java加密扩展),JAXP等,看JDK源码或者第三方源码的时候会经常碰到SPI,所以我觉得很有必要写个笔记把SPI记录下来。
定义SPI接口
这里我就拿JDBC的数据库驱动作为例子,这里Driver类进行了简化。
早期的JDBC需要使用DriverManager手动注册数据库驱动类,JDBC4.0之后要求数据库驱动必须在
META-INF/services/java.sql.Driver
文件中包含驱动类的全类名,以实现驱动类的自动注册(自动注册实际上就是用SPI实现)。
1 | // 这个地方不要用java作为包名 |
这里不要使用
java
作为包名,因为这个包名已经被JDK自己使用了,为了防止冲突Java平台不允许第三方使用这个作为包名,否则在后面的代码中会抛出SecurityException。
用以下命令编译这个接口,并打成jar包
1 | :: 编译SPI接口 |
实现SPI接口
MySQL驱动简单实现
1 | package mysql; |
为了能让Java平台发现这个SPI实现我们需要在META-INF/services
目录下以SPI接口全类名作为文件名,并在该文件中写下实现类全类名。
在这个例子中就是创建META-INF/services/javaxx.Driver
文件,并在文件中写下以下内容
1 | ## 文件中“#”号开头的内容会被解析成注释 |
我们把这个SPI实现打成jar包
1 | :: 编译Mysql SPI实现 |
Oracle驱动简单实现
1 | package oracle; |
在META-INF/services/javaxx.Driver
文件中写下Oracle SPI的实现类:
1 | ## java.Driver Oracle Implement |
编译打包:
1 | :: 编译Oracle实现 |
使用ServiceLoader加载SPI实现
在java.util
包下有个ServiceLoader工具类可以帮助我们加载类路径下所有的SPI接口的实现类
1 | package cn.hff; |
我们编译一下上面的Client类:
1 | :: 编译 |
为了能让ServiceLoader找到MySQL和Oracle的SPI,运行时需要在将前面编译的jar包添加到类路径子下:
1 | :: 运行 |