JDK中「SPI」原理分析
基于【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」原理分析的更多相关文章
- Dubbo SPI机制之一JDK中的SPI
首先简单阐述下什么是SPI:SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.目前有不少框架用它来做服务的扩展发现,简单来说,就是一种动态 ...
- 13万字详细分析JDK中Stream的实现原理
前提 Stream是JDK1.8中首次引入的,距今已经过去了接近8年时间(JDK1.8正式版是2013年底发布的).Stream的引入一方面极大地简化了某些开发场景,另一方面也可能降低了编码的可读性( ...
- Java中「Future」接口详解
目录 一.背景 二.Future接口 1.入门案例 2.Future接口 三.CompletableFuture类 1.基础说明 2.核心方法 2.1 实例方法 2.2 计算方法 2.3 结果获取方法 ...
- JDK动态代理案例与原理分析
一.JDK动态代理实现案例 Person接口 package com.zhoucong.proxy.jdk; public interface Person { // 寻找真爱 void findlo ...
- JDK中自带的JVM分析工具
目录 一.业务背景 二.Jdk-Bin目录 三.命令行工具 1.jps命令 2.jinfo命令 3.jstat命令 4.jstack命令 5.jmap命令 四.可视化工具 1.jconsole 2.v ...
- IO流中「线程」模型总结
目录 一.基础简介 二.同步阻塞 1.模型图解 2.参考案例 三.同步非阻塞 1.模型图解 2.参考案例 四.异步非阻塞 1.模型图解 2.参考案例 五.Reactor模型 1.模型图解 1.1 Re ...
- 关于boost中enable_shared_from_this类的原理分析
首先要说明的一个问题是:如何安全地将this指针返回给调用者.一般来说,我们不能直接将this指针返回.想象这样的情况,该函数将this指针返回到外部某个变量保存,然后这个对象自身已经析构了,但外部变 ...
- Python后端日常操作之在Django中「强行」使用MVVM设计模式
扫盲 首先带大家了解一下什么是MVVM模式: 什么是MVVM?MVVM是Model-View-ViewModel的缩写. MVVM是MVC的增强版,实质上和MVC没有本质区别,只是代码的位置变动而已 ...
- Dubbo的SPI机制与JDK机制的不同及原理分析
从今天开始,将会逐步介绍关于DUbbo的有关知识.首先先简单介绍一下DUbbo的整体概述. 概述 Dubbo是SOA(面向服务架构)服务治理方案的核心框架.用于分布式调用,其重点在于分布式的治理. 简 ...
- JDK中的SPI机制
前言 最近学习类加载的过程中,了解到JDK提供给我们的一个可扩展的接口:java.util.ServiceLoader, 之前自己不了解这个机制,甚是惭愧... 什么是SPI SPI全称为(Servi ...
随机推荐
- 【Linux】文件及用户组合权限管理
第二周1.显示/etc目录下,以非字母开头,后面跟了一个字母以及其它任意长度任意字符的文件或目录 ls -a /etc/[^[:alpha:]][:alpha:]* 2.复制/etc目录下所有以p开头 ...
- 第十四届蓝桥杯省赛C++ B组(个人经历 + 题解)
参赛感受 这是我第一次参加蓝桥杯的省赛,虽然没什么参赛经验,但是自己做了很多前几届蓝桥杯的题,不得不说,这一届蓝桥杯省赛的难度相较于之前而言还是比较大的.之前很流行蓝桥杯就是暴力杯的说法,但是随着参赛 ...
- pngquant 在 Windows 上压缩带中文路径的 png 图片
pngquant 是一个优秀的 png 压缩工具,但是在 Windows 上不支持目录中带有 unicode 字符(例如中文)的文件.所以要用一个折中的办法(即标准输入)让 pngquant 压缩目录 ...
- 2022-12-09:上升的温度。以下的数据输出2和4,2015-01-02 的温度比前一天高(10 -> 25),2015-01-04 的温度比前一天高(20 -> 30),sql语句如何写? DR
2022-12-09:上升的温度.以下的数据输出2和4,2015-01-02 的温度比前一天高(10 -> 25),2015-01-04 的温度比前一天高(20 -> 30),sql语句如 ...
- 2022-08-17:以下go语言代码输出什么?A:运行时 panic;B:32;C:编译错误;D:0。 package main func main() { var x *struct {
2022-08-17:以下go语言代码输出什么?A:运行时 panic:B:32:C:编译错误:D:0. package main func main() { var x *struct { s [] ...
- 2022-06-12:在N*N的正方形棋盘中,有N*N个棋子,那么每个格子正好可以拥有一个棋子。 但是现在有些棋子聚集到一个格子上了,比如: 2 0 3 0 1 0 3 0 0 如上的二维数组代表,一
2022-06-12:在NN的正方形棋盘中,有NN个棋子,那么每个格子正好可以拥有一个棋子. 但是现在有些棋子聚集到一个格子上了,比如: 2 0 3 0 1 0 3 0 0 如上的二维数组代表,一共3 ...
- 2021-11-14:Fizz Buzz。给你一个整数 n ,找出从 1 到 n 各个整数的 Fizz Buzz 表示,并用字符串数组 answer(下标从 1 开始)返回结果,其中:answer[i
2021-11-14:Fizz Buzz.给你一个整数 n ,找出从 1 到 n 各个整数的 Fizz Buzz 表示,并用字符串数组 answer(下标从 1 开始)返回结果,其中:answer[i ...
- 2014年蓝桥杯C/C++大学B组省赛真题(李白打酒)
题目描述: 题目描述 话说大诗人李白,一生好饮.幸好他从不开车. 一天,他提着酒壶,从家里出来,酒壶中有酒2斗.他边走边唱: 无事街上走,提壶去打酒. 逢店加一倍,遇花喝一斗. 这一路上,他一共遇到店 ...
- {"status":-1,"statusText":"ERR_CONNECT_FAILED"}
今日使用weex 的stream 遇到一个极坑,也极傻的问题 一.steam.fetch 下面是我使用steam.fetch调用后台接口都截图 二.页面测试 奇怪的是,我借同事是手机来进行测试,有一个 ...
- 深度解析 slab 内存池回收内存以及销毁全流程
在上篇文章 <深入理解 slab cache 内存分配全链路实现> 中,笔者详细地为大家介绍了 slab cache 进行内存分配的整个链路实现,本文我们就来到了 slab cache 最 ...