Service Provider Interface详解 (SPI)

本文详细介绍了Java的Service Provider Interface(SPI)机制,通过一个简单的示例展示如何使用SPI创建可扩展服务。内容包括创建接口和实现类,服务注册,以及通过单元测试验证SPI的自动加载功能。同时,文章深入剖析了SPI的源码,解释了ServiceLoader的工作原理,如懒加载迭代器的实现。最后,以JDBC4.0为例,说明SPI在实际应用中的作用,即自动加载数据库驱动。
摘要由CSDN通过智能技术生成

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"。

注意本步骤的要点:

  1. 必须放在JAR包或项目的指定路径,即 META-INF/services 下
  2. 必须以服务的全限定名命名配置文件,比如本例中,配置文件必须命名为 cn.jinlu.spi.demo.Animal,java会根据此名进行服务查找
  3. 内容必须是一个实现类的全限定名,如果要注册多个实现类,按行分割。注释以#开头。

2.5 增加单元测试:

注意,如果找不到@Test,可能是junit版本太低,在pom.xml中将其改为 4.0 或更高版本(maven-archetype-quickstart模板默认的JUNIT目前是3.8.1版本)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值