1.介绍
熟悉JDBC的同学都知道,在jdbc4.0之前,在使用DriverManager获取DB连接之前,我们总是需要显示的实例化DB驱动。比如,对mysql,典型的代码如下:
Connection conn = null;
Statement stmt = null;
try{
// 注册 JDBC driver
Class.forName("com.mysql.jdbc.Driver");
// 打开连接
conn = DriverManagger.getConnection(DB_URL,USER,PASSWD);
// 执行一条sql
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// 数据解包
while(ts.next()){
// 根据列名获取列值
// ...
} catch(SQLException se) {
// ...
} final {
try {
if (stmt!=null) stmt.close();
} catch(Exception e) {/*ignored*/}
try {
if (conn!=null) conn.close();
} catch(Exception e) {/*ignored*/}
}
}
JDBC的开始,总是需要通过Class.forName显式实例化驱动,否则将找不到对应DB的驱动。但是JDBC4.0开始,这个显式的初始化不再是必选项了,它存在的意义只是为了向上兼容。那么JDBC4.0之后,我们的应用是如何找到对应的驱动呢?
答案就是SPI(Service Provider Interface)。Java在语言层面为我们提供了一种方便地创建可扩展应用的途径。SPI提供了一种JVM级别的服务发现机制,我们只需要按照SPI的要求,在jar包中进行适当的配置,jvm就会在运行时通过懒加载,帮我们找到所需的服务并加载。如果我们一直不使用某个服务,那么它不会被加载,一定程度上避免了资源的浪费。
2.一个简单的例子
我们通过一个简单的例子看看如何最小化构建一个基于SPI的服务。
2.1 创建一个默认的maven项目
$ mvn archetype:generate -DgroupId=cn.jinlu.spi.demo -DartifactId=simplespi -Dversion=0.1-SNAPSHOT -DpackageName=cn.jinlu.spi.demo -DarchetypeArtifactId=maven-archetype-quickstart
...
[INFO] Using property: groupId = cn.jinlu.spi.demo
[INFO] Using property: artifactId = simplespi
[INFO] Using property: version = 0.1-SNAPSHOT
[INFO] Using property: package = cn.jinlu.spi.demo
Confirm properties configuration:
groupId: cn.jinlu.spi.demo
artifactId: simplespi
version: 0.1-SNAPSHOT
package: cn.jinlu.spi.demo
Y: :[回车]
2.2 添加一个interface或abstract class
Java SPI并没有强制必须使用interface或abstract class,完全可以将class注册为SPI注册服务,但是作为可扩展服务,使用interface或abstract class是一个好习惯。
在包 “cn.jinlu.spi.demo”中定义一个接口Animal:
package cn.jinlu.spi.demo;
public interface Animal {
void eat();
void sleep();
}
2.3 提供实现类
package cn.jinlu.spi.demo.impl;
import cn.jinlu.spi.demo.Animal;
public class Elephant implements Animal {
@Override
public void eat() {
System.out.println("Elephant is eating");
}
@Override
public void sleep() {
System.out.println("Elephant is sleeping");
}
}
2.4 服务注册
在main目录下创建目录 "resources/META-INF/services"
mkdir -p resources/META-INF/services
再在该目录下创建以接口Animal全限定名为名的配置文件,文件内容为该接口的实现类的全限定名,即
echo "cn.jinlu.spi.demo.impl.Elephant" > resources/META-INF/services/cn.jinlu.spi.demo.Animal
完成此步骤后,在当前maven项目的 src/main/resources/META-INF/services下有这么一个配置文件:"cn.jinlu.spi.demo.Animal",并且它的内容为"cn.jinlu.spi.demo.impl.Elephant"。
注意本步骤的要点:
- 必须放在JAR包或项目的指定路径,即 META-INF/services 下
- 必须以服务的全限定名命名配置文件,比如本例中,配置文件必须命名为 cn.jinlu.spi.demo.Animal,java会根据此名进行服务查找
- 内容必须是一个实现类的全限定名,如果要注册多个实现类,按行分割。注释以#开头。
2.5 增加单元测试:
注意,如果找不到@Test,可能是junit版本太低,在pom.xml中将其改为 4.0 或更高版本(maven-archetype-quickstart模板默认的JUNIT目前是3.8.1版本)。