首页 > 其他分享 >JDK中「SPI」原理分析

JDK中「SPI」原理分析

时间:2023-08-05 12:00:59浏览次数:42  
标签:return JDK service class private public SPI 原理 ServiceLoader

基于【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

标签:return,JDK,service,class,private,public,SPI,原理,ServiceLoader
From: https://blog.51cto.com/cicadasmile/6974540

相关文章

  • JDK中「SPI」原理分析
    目录一、SPI简介1、概念2、入门案例2.1定义接口2.2两个实现类2.3配置文件2.4测试代码二、原理分析1、ServiceLoader结构2、iterator迭代方法3、hasNextService方法4、nextService方法三、SPI实践1、Driver驱动接口2、Slf4j日志接口四、参考源码基于【JDK1.8】一、SPI简介......
  • 正点原子ARM裸机开发003----汇编LED驱动实验1-原理分析
    一、汇编LED原理分析为什么要学习Coretex-A汇编?需要用汇编初始化一些SOC外设使用汇编初始化DDR,IMX6U不需要设置sp指针,一般指向DDR,设置好C语言运行环境ALPHA开发板LED灯硬件原理分析:STM32 IO初始化流程:使能GPIO时钟设置IO复用,将其复用为GPIO配置GPIO的电气属性使用G......
  • 机器学习中模型泛化能力和过拟合现象(overfitting)的矛盾、以及其主要缓解方法正则化
    机器学习中模型泛化能力和过拟合现象(overfitting)的矛盾、以及其主要缓解方法正则化技术原理初探1.从多项式曲线拟合中的过拟合问题说起我们以一个简单的回归问题开始,说明许多关键的概念。假设我们观察到一个实值输入变量x,我们想使用这个观察来预测实值......
  • NNs(Neural Networks,神经网络)和Polynomial Regression(多项式回归)等价性之思考,以及深度
    NNs(NeuralNetworks,神经网络)和PolynomialRegression(多项式回归)等价性之思考,以及深度模型可解释性原理研究与案例1.MainPoint0x1:行文框架第二章:我们会分别介绍NNs神经网络和PR多项式回归各自的定义和应用场景。第三章:讨论NNs和PR在数学公式上的等价性,NNs......
  • mof提权原理及其过程——类似定时任务里有一个添加用户的命令
    关于mof提权的原理其实很简单,就是利用了c:/windows/system32/wbem/mof/目录下的 nullevt.mof 文件,每分钟都会在一个特定的时间去执行一次的特性,来写入我们的cmd添加提权用户命令使其被带入执行。 mof提权的原理:mof是windows系统的一个文件(在c:/windows/system32/wbem/mof/nul......
  • SSH原理与实践(三)安装和使用
    主页个人微信公众号:密码应用技术实战个人博客园首页:https://www.cnblogs.com/informatics/引言在之前SSH原理与实践系列文章中,我们主要讲解了SSH协议的原理部分。作为该系列文章的最后一篇,本文将对SSH实践部分进行介绍。好文回顾......
  • 作者推荐 | 【底层服务/编程功底系列】「底层技术原理」史上最清晰的采用程序员的视角
    背景介绍现在,零拷贝功能在Linux下几乎家喻户晓,但仍有很多人对其了解有限。为了解开这个功能的神秘面纱,我决定撰写一篇关于深入探讨的文章。本文将从用户模式应用程序的角度出发,介绍零拷贝的概念,省略了内核级的技术细节。希望通过本篇文章,可以帮助大家能更好地理解这个有用功能。什......
  • shell 脚本:nginx jdk maven node-exporter docker-ce
     写一个脚本,本地自带nginx1.24源码包,然后自动完成安装,并加入system管理。并设置开启自启动,并启动ng。并完成对80端口的curl测试,返回状态码200打印启动正常的消息:#!/bin/bash#安装依赖sudoapt-getupdatesudoapt-getinstall-ybuild-essentialwgetcurl#下载并解......
  • 线性方程组数学原理、矩阵原理及矩阵变换本质、机器学习模型参数求解相关原理讨论
    线性方程组数学原理、矩阵原理及矩阵变换本质、机器学习模型参数求解相关原理讨论1.线性方程组0x1:无处不在的线性方程组日常生活或生产实际中经常需要求一些量,用未知数x1,x2,....,xn表示这些量,根据问题的实际情况列出方程组,而最常见的就是线性方程组(当然并不......
  • IDEA官宣: 最低 JDK 17!2022.3 版本开始!
    JetBrains博客显示:IntelliJ项目的源代码最近迁移到了Java17。尽管仍然编译了一些模块与旧Java版本兼容,以支持在老版本Java下运行用户的项目。但现在大多数平台和插件模块都使用Java17,而即将推出的所有基于IntelliJ的IDE的2022.3版本都需要Java17才能启动。对用......