SPI 机制

一、简述

本文介绍 SPI 机制。

二、什么是 SPI 机制

SPI(Service Provider Interface)机制是 Java 编程语言中的一种机制,用于实现组件之间的解耦和扩展。SPI 允许开发者编写服务接口(Service Interface),并在运行时动态地加载实现了该接口的服务提供者(Service Provider)。
SPI 机制的基本原理如下:

  1. 定义服务接口:开发者定义一个服务接口,描述了一组操作或功能,这些功能可以由不同的提供者来实现。
  2. 服务提供者编写服务:不同的服务提供者可以编写不同的服务(即实现该服务接口,并提供自己的实现逻辑)。
  3. 配置服务注册文件:每个服务提供者都需要在特定的位置提供一个描述文件,通常是META-INF/services/接口全限定名,文件内容为该接口实现类的全限定类名列表。
  4. 服务加载器加载服务:在程序运行时,Java 虚拟机会通过 SPI 机制加载并实例化这些服务提供者,然后调用其方法来完成具体的功能。

三、为什么使用 SPI 机制

  1. 假设我们有一个服务接口 Animal,定义了动物的基本行为:
public interface Animal {
    void makeSound();
}
  1. Animal 接口有一个实现类
public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks: Woof woof!");
    }
}
  1. 编写接口和类的使用逻辑
public class Main {
    public void sound() {
        Animal animal = new Dog();
        animal.makeSound();
    }
}

上面的例子是传统的开发模式(在一个项目中定义接口,实现接口)。现在将以上内容拆分到两个项目:

  1. 在 A 项目中定义接口
public interface Animal {
    void makeSound();
}
  1. 在 A 项目中定义接口的使用逻辑(但是没有实现类)
import java.util.ServiceLoader;

public class Main {
    public void sound() {
        // 使用 ServiceLoader 加载 Animal 接口的实现类
        ServiceLoader<Animal> animals = ServiceLoader.load(Animal.class);

        // 遍历并调用每个实现类的方法
        for (Animal animal : animals) {
            animal.makeSound();
        }
    }
}
  1. B 项目中引入 A 项目,并且实现 A 中的接口
public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks: Woof woof!");
    }
}
  1. B 项目在 META-INF/services/Animal 文件中提供实现类的全限定类名:
com.example.Dog

上面两种方式可以实现同样的功能,第二种方式采用的是 SPI 机制。在 SPI 机制中,A 定义方法的执行逻辑,并开放了一个扩展点交由 B 实现,扩展点的具体功能是由实现者自己处理,这样就实现了解耦。可以看出 SPI 机制的优点在于它的松耦合性和扩展性,因为服务接口与具体的实现是分离的,开发者可以根据需要灵活地替换服务提供者接口的实现。

四、如何使用 SPI 机制

我们这里以 JDBC 为例来说明该如何使用 SPI。

4.1 定义服务接口

Java 团队在 java.sql 包中定义了 Driver 接口

package java.sql;

import java.util.logging.Logger;

public interface Driver {

    Connection connect(String url, java.util.Properties info)
        throws SQLException;

    boolean acceptsURL(String url) throws SQLException;

    DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
                         throws SQLException;

    int getMajorVersion();

    int getMinorVersion();

    boolean jdbcCompliant();

    public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}

4.2 服务加载器加载服务

Java 团队在 java.sql.DriverManager类中使用 java.util.ServiceLoader来加载 java.sql.Driver接口的实现类(可能不止一个),并实现了一些逻辑。

package java.sql;

import java.util.ServiceLoader;

public class DriverManager {

    // 此处省略了一些逻辑内容

    private static void ensureDriversInitialized() {

        // 服务加载器加载服务
        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        // 迭代遍历能够找到的服务实现
        Iterator<Driver> driversIterator = loadedDrivers.iterator();

        try {
            while (driversIterator.hasNext()) {
                driversIterator.next();
            }
        } catch (Throwable t) {
            // Do nothing
        }
        return null;
    }

    // 此处省略了一些逻辑内容
}

4.3 服务提供者编写服务

MySQL 团队在自己项目的 com.mysql.cj.jdbc 包中编写了针对 Java 团队 java.sql.Driver 接口的实现。

package com.mysql.cj.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

4.4 服务提供者配置服务注册文件

MySQL 团队在自己项目中 META-INF/services/ 目录下配置了服务注册文件。该文件名是接口的全限定名称,文件里面的内容是实现类的全限定名称。
image.png

4.5 SPI 机制处理流程

  1. Java 团队预先定义好接口,定义好接口的使用逻辑
  2. MySQL 团队实现接口,并以规定好的方式配置服务注册文件(即配置文件必须在 META-INF/services 目录下,文件名称以接口全限定名称命名,文件内容为接口实现类的全限定名称)
  3. 当在项目中引入了 MySQL 的驱动之后,JDBC 就能根据之前的服务注册文件找到对应的实现类
  4. 接下来,可以使用 JDBC 连接、操作数据库了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值