基于【JDK1.8】

一、SPI简介

1、概念

SPI即service-provider-interface的简写;

JDK内置的服务提供加载机制,可以为服务接口加载实现类,解耦是其核心思想,也是很多框架和组件的常用手段;

2、入门案例

2.1 定义接口

就是普通的接口,在SPI的机制中称为【service】,即服务;

public interface Animal {
String animalName () ;
}

2.2 两个实现类

提供两个模拟用来测试,就是普通的接口实现类,在SPI的机制中称为【service-provider】即服务提供方;

CatAnimal实现类;

public class CatAnimal implements Animal {
@Override
public String animalName() {
System.out.println("Cat-Animal:布偶猫");
return "Ragdoll";
}
}

DogAnimal实现类;

public class DogAnimal implements Animal {
@Override
public String animalName() {
System.out.println("Dog-Animal:哈士奇");
return "husky";
}
}

2.3 配置文件

文件目录:在代码工程中创建META-INF.services文件夹;

文件命名:butte.program.basics.spi.inf.Animal,即全限定接口名称;

文件内容:添加相应实现类的全限定命名;

butte.program.basics.spi.impl.CatAnimal
butte.program.basics.spi.impl.DogAnimal

2.4 测试代码

通过ServiceLoader加载配置文件中指定的服务实现类,然后遍历并调用Animal接口方法,从而执行不同服务提供方的具体逻辑;

public class SpiAnaly {
public static void main(String[] args) {
ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);
Iterator<Animal> animalIterator = serviceLoader.iterator();
while(animalIterator.hasNext()) {
Animal animal = animalIterator.next();
System.out.println("animal-name:" + animal.animalName());
}
}
}

结果输出

Cat-Animal:布偶猫 \n animal-name:ragdoll
Dog-Animal:哈士奇 \n animal-name:husky

二、原理分析

1、ServiceLoader结构

很显然,分析SPI机制的原理,从ServiceLoader源码中load方法切入即可,但是需要先从核心类的结构开始分析;

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;
// 创建ServiceLoader时采用的访问控制上下文
private final AccessControlContext acc;
// 按实例化的顺序缓存服务提供方
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 惰性查找迭代器
private LazyIterator lookupIterator;
/**
* service:表示服务的接口或抽象类
* loader: 类加载器
*/
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
/**
* ServiceLoader构造方法
*/
private ServiceLoader(Class<S> svc, ClassLoader cl) {
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
providers.clear();
// 实例化迭代器
lookupIterator = new LazyIterator(service, loader);
}
public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader) {
return new ServiceLoader<>(service, loader);
} private class LazyIterator implements Iterator<S> {
// 服务接口
Class<S> service;
// 类加载器
ClassLoader loader;
// 实现类URL
Enumeration<URL> configs = null;
// 实现类全名
Iterator<String> pending = null;
// 下个实现类全名
String nextName = null;
}
}

断点截图:

2、iterator迭代方法

ServiceLoader类的迭代器方法中,实际使用的是LazyIterator内部类的方法;

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();
}
};
}

3、hasNextService方法

从上面迭代方法的源码中可知,最终执行的是LazyIterator#hasNextService判断方法,该方法通过解析最终会得到实现类的全限定名称;

private class LazyIterator implements Iterator<S> {
private boolean hasNextService() {
// 1、拼接名称
String fullName = PREFIX + service.getName();
// 2、加载资源文件
configs = loader.getResources(fullName);
// 3、解析文件内容
pending = parse(service, configs.nextElement());
nextName = pending.next();
return true;
}
}

断点截图:

4、nextService方法

迭代器的next方法最终执行的是LazyIterator#nextService获取方法,会基于上面hasNextService方法获取的实现类全限定名称,获取其Class对象,进而得到实例化对象,缓存并返回;

private class LazyIterator implements Iterator<S> {
private S nextService() {
// 1、通过全限定命名获取Class对象
String cn = nextName;
Class<?> c = Class.forName(cn, false, loader);
// 2、实例化对象
S p = service.cast(c.newInstance());
// 3、放入缓存并返回该对象
providers.put(cn, p);
return p;
}
}

断点截图:

三、SPI实践

1、Driver驱动接口

在JDK中提供了数据库驱动接口java.sql.Driver,无论是MySQL驱动包还是Druid连接池,都提供了该接口的实现类,通过SPI机制可以加载到这些驱动实现类;

public class DriverManager {
private static void loadInitialDrivers() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(java.sql.Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
}
});
}
}

断点截图:

2、Slf4j日志接口

SLF4J是门面模式的日志组件,提供了标准的日志服务SLF4JServiceProvider接口,在LogFactory日志工厂类中,负责加载具体的日志实现类,比如常用的Log4j或Logback日志组件;

public final class LoggerFactory {
static List<SLF4JServiceProvider> findServiceProviders() {
// 服务加载
ClassLoader classLoaderOfLoggerFactory = org.slf4j.LoggerFactory.class.getClassLoader();
// 重点看该方法:【getServiceLoader()】
ServiceLoader<SLF4JServiceProvider> serviceLoader = getServiceLoader(classLoaderOfLoggerFactory);
// 迭代方法
List<SLF4JServiceProvider> providerList = new ArrayList();
Iterator<SLF4JServiceProvider> iterator = serviceLoader.iterator();
while(iterator.hasNext()) {
safelyInstantiate(providerList, iterator);
}
return providerList;
}
}

断点截图:

四、参考源码

文档仓库:
https://gitee.com/cicadasmile/butte-java-note 应用仓库:
https://gitee.com/cicadasmile/butte-flyer-parent

JDK中「SPI」原理分析的更多相关文章

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

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

  2. 13万字详细分析JDK中Stream的实现原理

    前提 Stream是JDK1.8中首次引入的,距今已经过去了接近8年时间(JDK1.8正式版是2013年底发布的).Stream的引入一方面极大地简化了某些开发场景,另一方面也可能降低了编码的可读性( ...

  3. Java中「Future」接口详解

    目录 一.背景 二.Future接口 1.入门案例 2.Future接口 三.CompletableFuture类 1.基础说明 2.核心方法 2.1 实例方法 2.2 计算方法 2.3 结果获取方法 ...

  4. JDK动态代理案例与原理分析

    一.JDK动态代理实现案例 Person接口 package com.zhoucong.proxy.jdk; public interface Person { // 寻找真爱 void findlo ...

  5. JDK中自带的JVM分析工具

    目录 一.业务背景 二.Jdk-Bin目录 三.命令行工具 1.jps命令 2.jinfo命令 3.jstat命令 4.jstack命令 5.jmap命令 四.可视化工具 1.jconsole 2.v ...

  6. IO流中「线程」模型总结

    目录 一.基础简介 二.同步阻塞 1.模型图解 2.参考案例 三.同步非阻塞 1.模型图解 2.参考案例 四.异步非阻塞 1.模型图解 2.参考案例 五.Reactor模型 1.模型图解 1.1 Re ...

  7. 关于boost中enable_shared_from_this类的原理分析

    首先要说明的一个问题是:如何安全地将this指针返回给调用者.一般来说,我们不能直接将this指针返回.想象这样的情况,该函数将this指针返回到外部某个变量保存,然后这个对象自身已经析构了,但外部变 ...

  8. Python后端日常操作之在Django中「强行」使用MVVM设计模式

    扫盲 首先带大家了解一下什么是MVVM模式: 什么是MVVM?MVVM是Model-View-ViewModel的缩写. MVVM是MVC的增强版,实质上和MVC没有本质区别,只是代码的位置变动而已 ...

  9. Dubbo的SPI机制与JDK机制的不同及原理分析

    从今天开始,将会逐步介绍关于DUbbo的有关知识.首先先简单介绍一下DUbbo的整体概述. 概述 Dubbo是SOA(面向服务架构)服务治理方案的核心框架.用于分布式调用,其重点在于分布式的治理. 简 ...

  10. JDK中的SPI机制

    前言 最近学习类加载的过程中,了解到JDK提供给我们的一个可扩展的接口:java.util.ServiceLoader, 之前自己不了解这个机制,甚是惭愧... 什么是SPI SPI全称为(Servi ...

随机推荐

  1. vCenter报错:Log Disk Exhaustion on 10

    vCenter报错:Log Disk Exhaustion on 10 1.问题现象: 巡检时发现 vCenter Server 中,错误显示为:Log Disk Exhaustion on 10(字 ...

  2. Navicat Premium 16 安装教程

    使用数据库时经常会使用到Navicat,码一个教程 转载自https://www.bilibili.com/read/cv21586676?spm_id_from=444.41.list.card_a ...

  3. 日增数据超10PB!揭秘沃尔玛Lakehouse架构选型之路

    沃尔玛系统产生了世界上最大和最多样化的数据集之一,每天数据增长超 10 PB. 来自许多不同的来源及其支持的后端系统,一系列大量的业务事件流被发送到主要由 Apache Kafka 支持的消息传递层. ...

  4. vue全家桶进阶之路27:Vue.js 3.0的下载和安装

    使用脚手架vue-cli创建vue3项目,创建前需要准备以下: 1.node.js环境 见:https://www.cnblogs.com/beichengshiqiao/p/17251233.htm ...

  5. vue全家桶进阶之路6:Vue的安装以及js引入

    1.安装 注意:Vue 不支持 IE8 及以下版本 创建一个文件夹用于下载引入Vue D:\BaiduSyncdisk\vue2 按照最新版本的Vue npm install vue 创建完成后便可以 ...

  6. 【GiraKoo】C++中static关键字的作用

    C++中static关键字的作用 在程序中良好的使用static,const,private等关键字,对于代码的健壮性有很大的帮助. 本文介绍的就是C++中static关键字的一些常见用法与区别.适合 ...

  7. 【GiraKoo】C++多线程消息分发架构

    [开源需求]C++多线程消息分发架构 项目[gi_messager] 在多线程环境中,为每个线程提供独立的消息队列 MessageLoop.注:主线程默认自动创建消息队列. MessageLoopCe ...

  8. 【Java】水果超市管理系统

    前言 说是个系统,看着像实训的产物,但实际上这是Java课程最后一个关于jdbc的大实验,yes,挺大的. 过程 看着视频里的一堆一堆的文件,逻辑混乱的讲解,我决定 我自己写这个系统 说干就干: 分析 ...

  9. R 数据可视化 : 热图

    本文作者蒋刘一琦,自嘲是一个有艺术追求的生信狗,毕业于浙江大学生物信息学专业,目前在复旦大学就读研究生,研究方向为宏基因组. 在生物信息领域我们常常使用 R 语言对数据可视化.在对数据可视化的时候,我 ...

  10. STL-array(ACM)

    1.C++ 11 出现的, C++98没有 2.可以作为数组元素 pair<array<int, 3>, int>pair<int a[3], int > 不能这样 ...