前言

最近学习类加载的过程中,了解到JDK提供给我们的一个可扩展的接口:java.util.ServiceLoader

之前自己不了解这个机制,甚是惭愧...

什么是SPI

SPI全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制,比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。

首先放个图:我们在“调用方”和“实现方”之间需要引入“接口”,可以思考一下什么情况应该把接口放入调用方,什么时候可以把接口归为实现方。

先来看看接口属于实现方的情况,这个很容易理解,实现方提供了接口和实现,我们可以引用接口来达到调用某实现类的功能,这就是我们经常说的api,它具有以下特征:

1.是对实现的说明(我可以给你提供什么)

2.组织上位于实现方所在的包中

3.实现和接口在一个包中

当接口属于调用方时,我们就将其称为spi,全称为:service provider interface,spi的规则如下:

1.是对实现的约束(要提供这个功能,实现者需要做那些事情)

2.组织上位于调用方所在的包中

3.实现位于独立的包中(也可认为在提供方中)

简而言之

API会告诉您特定的类/方法为您执行什么操作,而SPI则告诉您必须执行哪些操作才能符合要求。通常,API和SPI是分开的。例如,在JDBC中,Driver类是SPI的一部分:如果只想使用JDBC,则不需要直接使用它,但是实现JDBC驱动程序的每个人都必须实现该类。但是,有时它们会重叠。Connection接口既是SPI,又是API:您在使用JDBC驱动程序时通常会使用它,并且需要由JDBC驱动程序的开发人员来实现。

JDK SPI使用说明及示例

要使用SPI比较简单,只需要按照以下几个步骤操作即可:

1.在jar包的META-INF/services目录下创建一个以"接口全限定名"为命名的文件,内容为实现类的全限定名

2.接口实现类所在的jar包在classpath下

3.主程序通过java.util.ServiceLoader动态状态实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM

4.SPI的实现类必须带一个无参构造方法

举例1

下例是使用maven引入了mysql的依赖后执行的,MySQL驱动内的截图:



java.sql.Driver文件全部内容:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
public class SpiTest {

    public static void main(String[] args) {
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = loader.iterator();
while(iterator.hasNext()) {
Driver driver = iterator.next();
System.out.println("driver is " + driver.getClass() + ", classLoader is " + driver.getClass().getClassLoader());
}
System.out.println("当前上下文类加载器是:" + Thread.currentThread().getContextClassLoader());
System.out.println("ServiceLoader的类加载器是:" + ServiceLoader.class.getClassLoader());
}
}

运行结果:

driver is class com.mysql.jdbc.Driver, classLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
driver is class com.mysql.fabric.jdbc.FabricMySQLDriver, classLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
当前上下文类加载器是:sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader的类加载器是:null

SPI相关的类加载的逻辑

因为ServiceLoader位于java.util.ServiceLoader,所以这个类是会被启动类加载器所加载,然后我们分析一下ServiceLoader.load(Driver.class)的源码。

根据类加载的原理:如果一个类由类加载器A加载,那么这个类的依赖类也会被类加载器A加载(前提是这个依赖类尚未被加载过)。

当执行ServiceLoader.load(Driver.class),如果不使用线程上下文类加载器来打破双亲委托模型,那么该方法的关联类也会被启动类加载器加载。

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

所以我们看到334行获取了线程上下文类加载器,然后调用另一个重载的load方法去加载Driver(即所谓的Service)。

继续跟踪ServiceLoader.load(service, cl)的代码,会发现它依次执行了如下动作:

1.初始化了一个:ServiceLoader对象,new ServiceLoader<>(service, loader)

2.执行reload()

3.new LazyIterator(service, loader)

这里所有的loader都是线程上下文类加载器,它默认为系统类加载器。

这时,SpiTest中的ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);就执行完了,

跟着执行SpiTest中的Iterator<Driver> iterator = loader.iterator();

这里当执行iterator.hasNext()的时候,就会进入到刚才初始化的LazyIterator类中,执行其中的下列方法,

所以这也是为什么"在jar包的META-INF/services目录下创建一个以"接口全限定名"为命名的文件,内容为实现类的全限定名"

        private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//这里的PREFIX是一个常量:META-INF/services/
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;
}

仔细阅读内部类LazyIterator类的源码,就可以知道:

1.JDK是怎么读取META-INF/services目录下的内容

2.JDK是怎么加载这些SPI的类的

JDK SPI的不足

JDK SPI的使用很简单。也做到了基本的加载扩展点的功能。但JDK SPI有以下的不足:

1.需要遍历所有的实现,并实例化,然后我们在循环中才能找到我们需要的实现。

2.配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。

3.扩展如果依赖其他的扩展,做不到自动注入和装配

4.不提供类似于Spring的IOC和AOP功能

5.扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的JDK SPI不支持

JDK中的SPI机制的更多相关文章

  1. Dubbo SPI机制之一JDK中的SPI

    首先简单阐述下什么是SPI:SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.目前有不少框架用它来做服务的扩展发现,简单来说,就是一种动态 ...

  2. 【Java】深入理解Java中的spi机制

    深入理解Java中的spi机制 SPI全名为Service Provider Interface是JDK内置的一种服务提供发现机制,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用 ...

  3. 结合实战和源码来聊聊Java中的SPI机制?

    写在前面 SPI机制能够非常方便的为某个接口动态指定其实现类,在某种程度上,这也是某些框架具有高度可扩展性的基础.今天,我们就从源码级别深入探讨下Java中的SPI机制. 注:文章已收录到:https ...

  4. java中的SPI机制

    1 SPI机制简介 SPI的全名为Service Provider Interface.大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的.在java.util.ServiceLoader的文档里 ...

  5. Java 中的 SPI 机制是什么鬼?高级 Java 必须掌握!

    作者:sigangjun blog.csdn.net/sigangjun/article/details/79071850 SPI的全名为:Service Provider Interface,大多数 ...

  6. 一文搞懂Java/Spring/Dubbo框架中的SPI机制

    几天前和一位前辈聊起了Spring技术,大佬突然说了SPI,作为一个熟练使用Spring的民工,心中一紧,咱也不敢说不懂,而是在聊完之后赶紧打开了浏览器,开始的学习之路,所以也就有了这篇文章.废话不多 ...

  7. Skywalking-12:Skywalking SPI机制

    SPI机制 基本概述 SPI 全称 Service Provider Interface ,是一种服务发现机制.通过提供接口.预定义的加载器( Loader )以及约定俗称的配置(一般在 META-I ...

  8. 面试常问的dubbo的spi机制到底是什么?

    前言 dubbo是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力.作为spring cloud alibaba体系中重要的一部分,随着spring cloud alibaba在 ...

  9. Java中的SPI原理浅谈

    在面向对象的程序设计中,模块之间交互采用接口编程,通常情况下调用方不需要知道被调用方的内部实现细节,因为一旦涉及到了具体实现,如果需要换一种实现就需要修改代码,这违反了程序设计的"开闭原则& ...

随机推荐

  1. appium+python运行自动化测试提示“find_element() takes from 1 to 3 positional arguments but 14 were given”错误

    1.运行后提示"find_element() takes from 1 to 3 positional arguments but 14 were given",在网上找了很多解决 ...

  2. Django——Ajax发送请求验证用户名是否被注册

    场景: 用户注册的时候,输入用户名之后,Ajax发送请求到后端,后端验证该用户名是否已经被注册,然后返回到注册页面提示用户. 1.模型: from django.db import models cl ...

  3. Identity基于角色的访问授权

    详情访问官方文档 例如,以下代码将访问权限限制为属于角色成员的用户的任何操作 AdministrationController Administrator : [Authorize(Roles = & ...

  4. 第24篇-虚拟机对象操作指令之getfield

    getfield指令表示获取指定类的实例域,并将其值压入栈顶.其格式如下: getstatic indexbyte1 indexbyte2 无符号数indexbyte1和indexbyte2构建为(i ...

  5. 深入学习Composer原理(四)

    本系列第四篇文章,也是最后一篇 首先,我们先看看Composer的源码从哪里看起.当然,请您先准备好源码. composer init或者直接install之后,自动生成了一个vendor目录,这时您 ...

  6. MNIST手写数字识别:卷积神经网络

    代码 import torch from torchvision import datasets from torch.utils.data import DataLoader import torc ...

  7. requests接口自动化-assert断言

    断言,自动判断接口返回的结果与预期结果是否一致 from common.get_mysql import * def test_assert(): a=0 b=1 # assert a,'断言失败打印 ...

  8. Appium和Selenium的区别和联系

    https://www.cnblogs.com/lv-lxz/p/11118862.html https://blog.csdn.net/weixin_42139375/article/details ...

  9. appium和selenium不同与相同之处

    原文来自: https://www.cnblogs.com/zhengshuheng/p/6370398.html selenium是web端的自动化,appium是app端的自动化,它继承了webd ...

  10. 鸿蒙内核源码分析(fork篇) | 一次调用,两次返回 | 百篇博客分析OpenHarmony源码 | v45.03

    百篇博客系列篇.本篇为: v45.xx 鸿蒙内核源码分析(Fork篇) | 一次调用,两次返回 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内 ...