JDK中「SPI」原理分析

这篇具有很好参考价值的文章主要介绍了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简介

1、概念

SPI即service-provider-interface的简写;

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

2、入门案例

JDK中「SPI」原理分析

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

断点截图:

JDK中「SPI」原理分析

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

断点截图:

JDK中「SPI」原理分析

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

断点截图:

JDK中「SPI」原理分析

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

断点截图:

JDK中「SPI」原理分析

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

断点截图:

JDK中「SPI」原理分析文章来源地址https://www.toymoban.com/news/detail-625814.html

四、参考源码

文档仓库:
https://gitee.com/cicadasmile/butte-java-note

应用仓库:
https://gitee.com/cicadasmile/butte-flyer-parent

到了这里,关于JDK中「SPI」原理分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SPI扩展点在业务中的使用及原理分析

    SPI 全称Service Provider Interface。面向接口编程中,我们会根据不同的业务抽象出不同的接口,然后根据不同的业务实现建立不同规则的类,因此一个接口会实现多个实现类,在具体调用过程中,指定对应的实现类,当业务发生变化时会导致新增一个新的实现类,亦或是导致已经

    2024年02月05日
    浏览(37)
  • Dockerfile概念、镜像原理、制作及案例讲解

    Linux文件操作系统讲解 Docker网址:https://hub.docker.com 3.1 Dockerfile

    2024年02月12日
    浏览(31)
  • MyBatisPlus入门案例与简介

    这一节我们来学习下MyBatisPlus的入门案例与简介,这个和其他课程都不太一样,其他的课程都是先介绍概念,然后再写入门案例。而对于MyBatisPlus的学习,我们将顺序做了调整,主要的原因MyBatisPlus主要是对MyBatis的简化,所有我们先体会下它简化在哪,然后再学习它是什么,以

    2023年04月12日
    浏览(37)
  • Vuex的简介以及入门案例

    Vuex介绍 Vuex是一种状态管理模式,它专为Vue.js应用程序开发设计。使用Vuex能够更好地组织Vue.js应用中的代码,并使代码更容易理解和维护。 Vuex把应用的状态(数据)集中存储到一个全局的store对象中,并使用mutations(同步任务)和actions(异步任务)来修改状态。Vuex的数据流

    2024年02月07日
    浏览(36)
  • Spring MVC简介附入门案例

    目录   一、SpringMVC简介 1.1 MVC模型 1.2 SpringMVC 二、SpringMVC入门案例 2.1 创建项目 2.2 引入依赖和tomcat插件 2.3 修改web.xml文件  2.4 新建springmvc.xml文件 2.5 编写控制器  2.6 配置运行方式 2.7 运行测试  三、SpringMVC执行流程 3.1 SpringMVC的组件 3.2 组件的工作流程 往期专栏文章

    2024年02月09日
    浏览(54)
  • 【音视频原理】图像相关概念 ③ ( RGB 色彩简介 | RGB 排列 | YUV 色彩简介 | YUV 编码好处 )

    RGB 是 计算机 中的 颜色编码方法 , 红 ( R ) / 绿 ( G ) / 蓝 ( B ) 三个颜色通道 可以设置不同的值 , 每个 通道 的 颜色值都可以取值 0 ~ 255 , 这样 三个通道 叠加 , 可以表示出 25 6 3 = 16777216 256 ^3 = 16777216 25 6 3 = 16777216 种颜色值 ; 红 ( R ) / 绿 ( G ) / 蓝 ( B ) 三个颜色通道 都取值 255

    2024年01月20日
    浏览(61)
  • 通信原理 | 累积谱的概念 | Python案例代码介讲解

    累积谱(Cumulative Spectrum)是信号处理领域的一个概念,它用于描述信号的频谱分布。具体来说,累积谱是一个函数或者图表,用来表示信号频谱中的能量分布,通常是从最低频率开始累积到某一特定频率的能量总和。这个概念可以帮助分析和理解信号的频率特性,特别是在噪

    2024年01月23日
    浏览(32)
  • sqoop入门简介 | 安装部署 | sqoop案例展示

    Sqoop (发音:skup)是一款开源的工具,主要用于在Hadoop(Hive)与传统的数据库(mysql、postgresql…)间进行数据的传递,可以将一个关系型数据库(例如 : MySQL ,Oracle ,Postgres等)中的数据导进到Hadoop的HDFS中,也可以将HDFS的数据导进到关系型数据库中。 Sqoop项目开始于2009年,最早是作

    2024年02月04日
    浏览(42)
  • 【MyBatis-Plus】入门案例与简介

    1. 入门案例 MybatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提供效率。 开发方式 基于MyBatis使用MyBatisPlus 基于Spring使用MyBatisPlus 基于SpringBoot使用MyBatisPlus SpringBoot刚刚我们学习完成,它能快速构建Spring开发环境用以整合其他技术,使用起来是非常简

    2024年02月10日
    浏览(41)
  • Git简介与工作原理:了解Git的基本概念、版本控制系统和分布式版本控制的工作原理

    🌷🍁 博主 libin9iOak带您 Go to New World.✨🍁 🦄 个人主页——libin9iOak的博客🎐 🐳 《面试题大全》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 🌊 《IDEA开发秘籍》学会IDEA常用操作,工作效率翻倍~💐 🪁🍁 希望本文能够给您带来一定的帮助🌸文章粗浅,敬

    2024年02月16日
    浏览(64)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包