Java SPI的介绍、JDBC中SPI的应用、自己实现一个SPI应用

1. Java SPI介绍

SPI(Service Provider Interface)是一种基于ClassLoader来发现并加载服务的机制。用于实现各种插件或灵活替换框架所使用的组件。设计思想采用:面向接口 + 配置文件 + 反射技术

一个标准的SPI,由以下3部分构成:

  • Service:一个公开的接口或抽象类,定义了一个抽象的功能模块
  • Service Provider:Service接口的一个实现类
  • ServiceLoader:是SPI机制中的核心组件,负责在运行时发现并加载Service Provider

优点:基于面向接口编程,实现了模块之间的解耦

应用场景:JDBC、slf4j、Servlet容器初始化等

2. Java SPI的运行流程

Java SPI的运行流程

3. Java SPI在JDBC中的应用

JDBC(Java DataBase Connectivity)是Java语言来访问数据库的一套API,每个数据库厂商都会提供各自的JDBC实现

JDBC访问

以前我们使用JDBC访问Mysql数据库的时候,都会执行Class.forName("com.mysql.cj.jdbc.Driver"),这样就会实例化驱动类,同时也会执行驱动类中的static代码块中的DriverManager.registerDriver(new Driver()),将驱动类注册到Drivermanager中

但是将驱动类的全路径名称写到代码中,非常不方便。如果将类的全路径名写到配置文件中,虽然方便,但是还需要我们去记驱动类的全路径名

使用Java的SPI就可以解决这个问题。让Mysql驱动提供方同时提供驱动包和驱动配置文件,Java SPI通过类ClassLoader(通过getResource/getResources从指定位置读取classpath中的配置文件、可以加载类)自动从指定位置读取配置文件并进行驱动类的加载。这样就不用我们进行驱动类的加载了,而且Mysql驱动提供方能很方便的进行驱动的升级

4. Java SPI的三大规范要素

  1. Service Provider类必须具备无参的默认构造方法

因为会通过反射技术实例化Service Provider,是不带参数的

Mysql的驱动类实现如下:

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!");
        }
    }
}
  1. 规范的配置文件
  • 配置文件路径:必须在jar包中的META-INF/services目录下
  • 配置文件名称:Service接口的全路径名
  • 配置文件内容:Service Provider类的全路径名。如果有Service实现类(Service Provider类),那么每一个Service Provider类的全路径名在文件中单独占一行

Mysql驱动的配置文件如下所示:

Mysql的驱动配置文件

  1. 保证能加载到配置文件和Service Provider类
  • 方式一:将Service Provider的jar包放到classpath中。该方式最常用,通过maven引入依赖也是这种方式。程序运行时自动加载配置文件和Service Provider类。虽然能加载Service Provider类,但是不能引用Service Provider类,可以参考Mysql的驱动类进行注册获取Service Provider类的引用
  • 方式二:将Service Provider的jar包放到jre的扩展目录中。程序运行时自动加载配置文件和Service Provider类。虽然能加载Service Provider类,但是不能引用Service Provider类,可以参考Mysql的驱动类进行注册获取Service Provider类的引用
  • 方式三:自定义一个ClassLoader。需要我们自己在程序中实现。程序运行时自动加载配置文件和Service Provider类。这种方式不但能加载Service Provider类,还能获取到Service Provider类

5. 自己实现一个SPI应用

应用背景:手机需要连接网络,定义一个连接网络的Service接口。由运营商1和运营商2实现Service接口来提供具体的网络服务

我们先在IDEA创建一个项目javaSpiTest

5.1 Service接口

在项目下面创建一个module,名称为network。定义一个NetworkService接口,内容如下:

package com.hh.network;

public interface NetworkService {

    void connectNetwork();
}

5.2 运营商1的Service Provider

在项目下面创建一个module,名称为operator1,依赖module network。定义一个BeijingNetworkServiceProvider类,内容如下:

package com.hh.operator1;

import com.hh.network.NetworkService;

import java.io.UnsupportedEncodingException;

public class BeijingNetworkServiceProvider implements NetworkService {

    @Override
    public void connectNetwork() {

        try {
            System.out.println(
                    new String("operator1提供的北京网络".getBytes(), "UTF-8")
            );
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

    }
}

再定义一个ShanghaiNetworkServiceProvider类,内容如下:

package com.hh.operator1;

import com.hh.network.NetworkService;

import java.io.UnsupportedEncodingException;

public class ShanghaiNetworkServiceProvider implements NetworkService {

    @Override
    public void connectNetwork() {

        try {
            System.out.println(
                    new String("operator1提供的上海网络".getBytes(), "UTF-8")
            );
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

    }
}

配置文件:在resources目录下新建文件META-INF/services/com.hh.network.NetworkService,内容如下:

com.hh.operator1.BeijingNetworkServiceProvider
com.hh.operator1.ShanghaiNetworkServiceProvider

5.3 运营商2的Service Provider

在项目下面创建一个module,名称为operator2,依赖module network。定义一个GuangzhouNetworkServiceProvider类,内容如下:

package com.hh.operator2;

import com.hh.network.NetworkService;

import java.io.UnsupportedEncodingException;

public class GuangzhouNetworkServiceProvider implements NetworkService {

    @Override
    public void connectNetwork() {

        try {
            System.out.println(
                    new String("operator2提供的广州网络".getBytes(), "UTF-8")
            );
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

    }
}

配置文件:在resources目录下新建文件META-INF/services/com.hh.network.NetworkService,内容如下:

com.hh.operator2.GuangzhouNetworkServiceProvider

5.3 手机使用网络

在项目下面创建一个module,名称为phone,依赖module operator1。定义一个PhoneNetworkUse类,内容如下:

package com.hh.phone;

import com.hh.network.NetworkService;

import java.util.ServiceLoader;

public class PhoneNetworkUse {

    public static void main(String[] args) {

        ServiceLoader<NetworkService> serviceLoader =
                ServiceLoader.load(NetworkService.class);

        for(NetworkService networkService: serviceLoader) {
            networkService.connectNetwork();
        }

    }
}

运行程序,结果如下:

operator1提供的北京网络
operator1提供的上海网络

如果将依赖module operator1,改成依赖module operator2。再运行程序,结果如下:

operator2提供的广州网络

猜你喜欢

转载自blog.csdn.net/yy8623977/article/details/125628698
今日推荐