前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >JDBC【3】-- SPI技术以及数据库连接中的使用

JDBC【3】-- SPI技术以及数据库连接中的使用

原创
作者头像
秦怀杂货店
修改于 2020-11-23 03:37:53
修改于 2020-11-23 03:37:53
6430
举报
文章被收录于专栏:技术杂货店技术杂货店

1.SPI是什么?

SPI,即是Service Provider Interface,是一种服务提供(接口实现)发现机制,可以通过ClassPath路径下的META-INF/Service文件查找文件,加载里面定义的类。

一般可以用来启用框架拓展和替换组件,比如在最常见的数据库连接JDBC中,java.sql.Driver,不同的数据库产商可以对接口做不一样的实现,但是JDK怎么知道别人有哪些实现呢?这就需要SPI,可以查找到接口的实现,对其进行操作。

用两个字解释:解耦

2.如何使用SPI来提供自定义服务?

我们来写一个简单的例子:

整个项目结构:

  • SPI-Project:maven项目
    • DBInterface:maven项目,parent是SPI-Project,定义了一个接口com.aphysia.sqlserver.DBConnectionService,自己不做实现。
    • MysqlConnection:prarent是SPI-Project,实现了接口DBConnectionService,也就是MysqlConnectionServiceImpl
    • SqlServerConnection:prarent 也是SPI-Project,实现了DBConnectionService,也就是SqlServerConnectionServiceImpl
    • WebProject:测试项目,模拟web项目里面使用数据库驱动。

不管是MySqlConnection还是SqlServerConnection两个module中,都是去实现了DBInterface的接口,并且在resource/META-INF/services下都需要声明所实现的类,文件名就是实现的接口全限定名com.aphysia.sql.DBConnectionService,文件里面就是具体的实现类的全限定名,比如:com.aphysia.mysql.MysqlConnectionServiceImpl

SPI-Project的pom文件:

代码语言:txt
AI代码解释
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.aphysia</groupId>
    <artifactId>SPI-Project</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <modules>
        <module>DbInterface</module>
        <module>MySqlConection</module>
        <module>SqlServerConnection</module>
        <module>WebProject</module>
    </modules>
</project>

2.1 DBInterface定义接口

DBInterface是SPIProject的一个module,主要是定义一个规范(接口),不做任何实现。

pom文件如下:

代码语言:txt
AI代码解释
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SPI-Project</artifactId>
        <groupId>com.aphysia</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>DbInterface</artifactId>
</project>

定义的接口(模拟了java提供的数据库驱动的情景,定义了驱动规范):DBConnectionService.java

代码语言:txt
AI代码解释
复制
package com.aphysia.sql;
public interface DBConnectionService {
    void connect();
}

2.2 模拟Mysql实现驱动

接口的第一种实现,相当于模拟第三方Mysql对接口做了自己的拓展:

pom文件:

代码语言:txt
AI代码解释
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SPI-Project</artifactId>
        <groupId>com.aphysia</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>MySqlConection</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.aphysia</groupId>
            <artifactId>DbInterface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

实现了前面定义的接口:

MysqlConnectionServiceImpl

代码语言:txt
AI代码解释
复制
package com.aphysia.mysql;

import com.aphysia.sqlserver.DBConnectionService;

public class MysqlConnectionServiceImpl implements DBConnectionService {
    public void connect() {
        System.out.println("mysql 正在连接...");
    }
}

声明实现,在resource/META-INF.services/下定义一个文件,名为com.aphysia.sql.DBConnection,里面内容是:

代码语言:txt
AI代码解释
复制
com.aphysia.mysql.MysqlConnectionServiceImpl

2.3 模拟SqlServer实现驱动

SqlServerConnection也是一个module,pom文件如下:

代码语言:txt
AI代码解释
复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SPI-Project</artifactId>
        <groupId>com.aphysia</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>SqlServerConnection</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.aphysia</groupId>
            <artifactId>DbInterface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

接口的第二种实现,相当于第三方SqlServer对接口做了自己的拓展:SqlServerConnectionServiceImpl

代码语言:txt
AI代码解释
复制
package com.aphysia.sqlserver;

public class SqlServerConnectionServiceImpl implements DBConnectionService {
    public void connect() {
        System.out.println("sqlServer 正在连接...");
    }
}

声明实现,在resource/META-INF.services/下定义一个文件,名为com.aphysia.sql.DBConnection,里面内容是:

代码语言:txt
AI代码解释
复制
com.aphysia.sqlserver.SqlServerConnectionServiceImpl

2.4 模拟用户使用不同驱动

上面两种不同的接口实现,注意需要在resource下声明,文件名是基类的全限定名,里面内容是具体实现类的全限定名

而我们自己使用项目的时候呢?肯定是需要哪一个驱动就引入哪一个驱动的jar包。

比如我们在webProject中导入两种实现:MysqlConnectionSqlServerConnection:

代码语言:txt
AI代码解释
复制
    <dependencies>
        <dependency>
            <groupId>com.aphysia</groupId>
            <artifactId>DbInterface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.aphysia</groupId>
            <artifactId>MySqlConection</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.aphysia</groupId>
            <artifactId>SqlServerConnection</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

测试代码如下:

代码语言:txt
AI代码解释
复制
import com.aphysia.sql.DBConnectionService;

import java.util.ServiceLoader;

public class Test {
    public static void main(String[] args) {

        ServiceLoader<DBConnectionService>  serviceLoader= ServiceLoader.load(DBConnectionService.class);
        for (DBConnectionService dbConnectionService : serviceLoader) {
            dbConnectionService.connect();
        }
    }
}

输出:

代码语言:txt
AI代码解释
复制
mysql 正在连接...
sqlServer 正在连接...

如果我们只在pom文件里面引入mysql的实现呢?答案很明显,只会输出下面一句:

代码语言:txt
AI代码解释
复制
mysql 正在连接...

也就是对于使用的人来说,不需要自己再做什么操作,只需要把包引入进来即可,简单易用。

具体完整代码: https://github.com/Damaer/DemoCode/tree/main/SPI-Project,仅供参考

3. ServiceLoader实现原理

ServiceLoader位于java.util包下,其主要代码如下:

代码语言:txt
AI代码解释
复制
public final class ServiceLoader<S>
    implements Iterable<S>
{
    private static final String PREFIX = "META-INF/services/";
    private final Class<S> service;

    private final ClassLoader loader;

    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

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

    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }


    private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        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
        }

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }
    public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        ClassLoader prev = null;
        while (cl != null) {
            prev = cl;
            cl = cl.getParent();
        }
        return ServiceLoader.load(service, prev);
    }

    public String toString() {
        return "java.util.ServiceLoader[" + service.getName() + "]";
    }

}

我们调用ServiceLoader.load()获取接口的实现,实际上也是调用了ServiceLoader(Class<S> svc, ClassLoader cl),里面都是调用reload()reload()里面做了些什么操作呢?

先把provider清空,然后创建了LazyIterator对象,LazyIterator是一个内部类,实现了Iterator接口,实际上就是一个懒加载的迭代器。什么时候加载呢?

在迭代器调用的时候,调用hasNextService(),去解析resource/META-INF/services下面的实现,并完成实现类的实例化。这里的实例化是使用反射,也是通过全限定类名。class.forName()

解析的时候,每一行代表一个实现类,将已经发现的接口进行缓存,放到private LinkedHashMap<String,S> providers中,同时对外提供遍历迭代的方法。

4. SPI的应用

我们在使用mysql驱动的时候,在mysql-connector-java-version.jar中,有一个文件是Resource/service/java.sql.Driver文件,里面记录的是:

代码语言:txt
AI代码解释
复制
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

也就是声明了java.sql.Driver的实现类是com.mysql.jdbc.Driver,不需要手动使用Class.forName()手动加载。

同样的,slf4j也是一样的机制去实现拓展功能。

这种思想,通过服务约定-->服务实现-->服务自动注册-->服务发现和使用,完成了提供者和使用方的解耦,真的很强...

【作者简介】

秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。这个世界希望一切都很快,更快,但是我希望自己能走好每一步,写好每一篇文章,期待和你们一起交流。

此文章仅代表自己(本菜鸟)学习积累记录,或者学习笔记,如有侵权,请联系作者核实删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有错误之处,还望指出,感激不尽~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
JDBC【4】-- SPI底层原理解析
SPI,即是Service Provider Interface,是一种服务提供(接口实现)发现机制,可以通过ClassPath路径下的META-INF/Service文件查找文件,加载里面定义的类。
秦怀杂货店
2020/11/28
4870
JDBC【4】-- SPI底层原理解析
不懂Java SPI机制,怎么进大厂
在日常的项目开发中,我们为了提升程序的扩展性,经常使用面向接口的编程思想进行编程。这不仅体现了程序设计对于修改关闭,对于扩展开放的程序设计原则,同时也实现了程序可插拔。那么本文所阐述的SPI机制正是这种编程思想的体现。今天就和大家聊聊SPI到底是个什么鬼。顺便和大家一起看下Seata框架中是怎么使用SPI机制来实现框架扩展的。
慕枫技术笔记
2023/03/20
6920
不懂Java SPI机制,怎么进大厂
锦囊篇|Java中的SPI机制
Service Provider Interface翻译成中文就是服务提供接口,简称SPI,它是JDK内置的一种机制,用途就是本地服务发现和提供。
ClericYi
2020/09/10
5050
锦囊篇|Java中的SPI机制
Dubbo源码学习--环境搭建及基础准备(ServiceLoader、ExtensionLoader)
该文介绍了Dubbo的SPI以及其拓展点实现,对Dubbo的SPI机制和拓展点进行了详细的分析和讲解,并提供了相关代码和实现。
YGingko
2017/12/28
1.3K0
Dubbo源码篇05---SPI神秘的面纱---使用篇
SPI全称是Service Provider Interface,其中服务提供者定义一个服务接口,并允许第三方实现进行插入。这种机制常用于预留一些关键口子或扩展点,以让调用方按照规范进行自由实现。
大忽悠爱学习
2023/10/11
4340
Dubbo源码篇05---SPI神秘的面纱---使用篇
SPIJava SPIDubbo SPI案例原理
SPI 全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制。一个服务(Service)通常指的是已知的接口或者抽象类,服务提供方就是对这个接口或者抽象类的实现,然后按照SPI 标准存放到资源路径META-INF/services目录下,文件的命名为该服务接口的全限定名。
spilledyear
2018/12/18
6090
Java-深入理解ServiceLoader类与SPI机制
对于Java中的Service类和SPI机制的透彻理解,也算是对Java类加载模型的掌握的不错的一个反映。
Fisherman渔夫
2020/02/17
3.2K0
JDBC【3】-- SPI技术以及数据库连接中的使用
SPI,即是Service Provider Interface,是一种服务提供(接口实现)发现机制,可以通过ClassPath路径下的META-INF/Service文件查找文件,加载里面定义的类。一般可以用来启用框架拓展和替换组件,比如在最常见的数据库连接JDBC中,java.sql.Driver,不同的数据库产商可以对接口做不一样的实现,但是JDK怎么知道别人有哪些实现呢?这就需要SPI,可以查找到接口的实现,对其进行操作。用两个字解释:解耦。
秦怀杂货店
2022/02/15
5910
JDBC【3】-- SPI技术以及数据库连接中的使用
你了解过Java的SPI机制吗?再不了解你就Out了!
SPI在Java中的全称为Service Provider Interface,是JDK内置的一种服务提供发现机制,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
冰河
2020/10/29
9060
你了解过Java的SPI机制吗?再不了解你就Out了!
java Spi与SpringFactoriesLoader
spring的SpringFactoriesLoader是spring框架内部工具类,在 Spring boot 应用启动的过程中,这个类的工作很重要, 启动逻辑使用该类从classpath上所有jar包中找出各自的 META-INF/spring.factories 属性文件,并分析出其中定义的工厂类。这些工厂类进而被启动逻辑使用,应用于进一步初始化工作。其使用的方式和java的spi基本类似,我们可以先学习java的spi而且进一步学习SpringFactoriesLoader。
用户5546570
2021/02/22
4510
java Spi与SpringFactoriesLoader
Java SPI机制总结系列之万字详细图解Java SPI机制源码分析
我在《Java SPI机制总结系列之开发入门实例》一文当中,分享了Java SPI的玩法,但是这只是基于表面的应用。若要明白其中的原理实现,还需深入到底层源码,分析一番。
朱季谦
2023/11/11
1.1K1
Java SPI机制总结系列之万字详细图解Java SPI机制源码分析
详解Java—ServiceLoader之源码分析
其构造方法是一个private方法,不对外提供,在使用时我们需要调用其静态的load方法,由其自身产生ServiceLoader对象:
本人秃顶程序员
2019/05/17
1.3K0
详解Java—ServiceLoader之源码分析
SPI都不知道?还敢说懂Dubbo?面试官怼的我哑口无言啊!!!
 为什么要讲SPI呢?因为在Dubbo就用到了SPI机制,所以掌握了这部分对于后面的学习还是很有帮助的。
用户4919348
2021/07/29
3540
SPI都不知道?还敢说懂Dubbo?面试官怼的我哑口无言啊!!!
你说说对Java中SPI的理解吧
最近在面试的时候被问到SPI了,没回答上来,主要也是自己的原因,把自己给带沟里去了,因为讲到了类加载器的双亲委派模型,后面就被问到了有哪些是破坏了双亲委派模型的场景,然后我就说到了SPI,JNDI,以及JDK9的模块化都破坏了双亲委派。 然后就被问,那你说说对Java中的SPI的理解吧。然后我就一脸懵逼了,之前只是知道它会破坏双亲委派,也知道是个怎么回事,但是并没有深入了解,那么这次我就好好的来总结一下这个知识吧。
纪莫
2020/12/08
3460
深入理解ServiceLoader类与SPI机制
最近我们自己在重构项目,系统为了符合82原则(希望是80%的业务能通过穷举的方式固定下来,只有20%的允许特殊的定义),那么在固定一些标准流程以后,比如我们放大了原子服务的能力,当放大原子服务能力的时候,你就会发现,虽然抽象上看做的事情是一个意思,但是到实际去实现的时候发现还是各不相同。
chengcheng222e
2021/11/04
4950
温习 SPI 机制 (Java SPI 、Spring SPI、Dubbo SPI)
SPI 全称为 Service Provider Interface,是一种服务发现机制。
勇哥java实战
2024/10/29
2460
温习 SPI 机制 (Java SPI 、Spring SPI、Dubbo SPI)
一文读懂微内核架构
微内核是一种典型的架构模式 ,区别于普通的设计模式,架构模式是一种高层模式,用于描述系统级的结构组成、相互关系及相关约束。微内核架构在开源框架中的应用非常广泛,比如常见的 ShardingSphere 还有Dubbo都实现了自己的微内核架构。那么,在介绍什么是微内核架构之前,我们有必要先阐述这些开源框架会使用微内核架构的原因。
JAVA日知录
2021/01/04
3.2K0
一文读懂微内核架构
【Java——SPI机制详解】
SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦。
奥耶可乐冰
2024/05/31
1.7K0
「周末福报」你了解 SPI 吗?
SPI(Service Provider Interface)是一种 JDK 提供的服务发现机制。
FoamValue
2020/09/01
3860
「周末福报」你了解 SPI 吗?
Java SPI机制
SPI全称Service Provider Interface,翻译过来是服务提供接口。它是jdk内置的一种服务发现机制,它可以让服务定义与实现分离、解耦,大大提升了程序的扩展性。Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,提供了通过interface寻找implement的方法。
@阿诚
2020/09/01
8300
相关推荐
JDBC【4】-- SPI底层原理解析
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文