IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 纳尼?这就是SPI -> 正文阅读

[Java知识库]纳尼?这就是SPI

引入

我们知道每种数据库都是一个完整的独立的系统,假设我们现在的产品要兼容大多数数据库厂商,比如MySQL,Oracle,DB2等,那么怎么做呢?可以制作一些约定,大家都基于这个约定进行相关操作,比如调用者可以通过约定去连接数据库,而数据库厂商则根据这个约定实现连接数据库的具体操作。
在这里插入图片描述

这个看起来有点像设计模式中的门面模式,调用者只需要对这个约定进行相关操作,而具体的实现由相应的厂商实现,假设要从MySQL切换到DB2,只需要在底层悄悄的替换即可,那么具体是怎么替换的呢?

在maven项目中,假设我们目前使用的是MySQL,那么我们的maven依赖是不是有 mysql-connector-java ,换言之,我们可以把这个依赖替换成我们想要的数据库依赖

那么它到底是怎么实现替换依赖就可以切换数据库的呢
以MySQL为例,打开MySQL的jar,看到了META-INFcom 目录
在这里插入图片描述
进入META-INF/services/ 目录下,发现了 java.sql.Driver 文件
在这里插入图片描述
查看就发现了我们熟悉的驱动,而这个驱动就是MySQL厂商实现的
在这里插入图片描述
那他是怎么和我们的java应用程序相关联的呢,通过源码,我们发现是实现了java的Driver接口

package com.mysql.cj.jdbc; 

import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

这个接口正好是jdk的拓展包里的
在这里插入图片描述
现在可以知道上面描述的约定便是这个 java.sql.Driver 接口。大家都基于这个约定进行相应的操作
这个便是我们常说的SPI机制

SPI

什么是SPI

SPI的全名为Service Provider Interface,当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。

JAVA中的实现

在Java中的实现就是ServiceLoader(源码部分省略)

public final class ServiceLoader<S>
    implements Iterable<S>
{
    private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    private final Class<S> service;

    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;
    
    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;
    .........
}

简单来说就是会在jar包下寻找 META-INF/services/ 目录下的文件,文件名就是接口名,文件内容就是这个接口的实现类,可以有多个实现类。

解析文件的内容

private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));//必须是utf-8的格式
            int lc = 1;
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        } finally {
            try {
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        return names.iterator();
    }

在这里进行加载,并且放进缓存中

private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        c = Class.forName(cn, false, loader); //通过反射加载类,但不进行初始化,也就是懒加载的
    } catch (ClassNotFoundException x) {
        fail(service,
             "Provider " + cn + " not found");
    }
    if (!service.isAssignableFrom(c)) {
        fail(service,
             "Provider " + cn  + " not a subtype");
    }
    try {
        S p = service.cast(c.newInstance());
        providers.put(cn, p); //放进缓存中
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}

实战模拟

这是一个约定,在一个单独的模块中,打jar包

public interface IDataBase {
    String getDataBaseName();
}

这也是一个单独的模块,依赖上面制定的约定,实现DB2数据库,打jar包

public class DB2 implements IDataBase {
    public String getDataBaseName() {
        return "db2";
    }
}

这也是一个单独的模块,依赖上面制定的约定,实现MySQL数据库,打jar包

public class Mysql implements IDataBase{
    public String getDataBaseName() {
        return "mysql";
    }
}

在这里插入图片描述

测试

客户端依赖

<dependencies>
        <dependency>
            <groupId>com.luo</groupId>
            <artifactId>db-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--使用DB2-->
        <dependency>
            <groupId>com.luo</groupId>
            <artifactId>db2</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
public class Test {
    public static void main(String[] args) {
        ServiceLoader<IDataBase> dataBases = ServiceLoader.load(IDataBase.class);//加载指定类的所有实现
        Iterator<IDataBase> iterator = dataBases.iterator();
        while (iterator.hasNext()){  //遍历每一个实现
            IDataBase next = iterator.next();
            System.out.println(next.getDataBaseName());
        }
    }
}

因为我只依赖了DB2,所以输出db2
在这里插入图片描述

优缺点

优点:解耦合,懒加载

缺点:会一次性加载配置文件中的所有实现类

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-12-24 18:20:30  更:2021-12-24 18:20:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:59:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码