【spring系列】SPI详解

news2024/11/26 10:48:11

1.什么是SPI

 SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。

2.SPI和API的使用场景

API (Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。
SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。

3、双亲委派的问题

从JDK1.6开始引入SPI机制

双亲委派的缺点

从类加载说起,案例:Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JNDI、JAXP等,这些SPI的接口由核心类库提供,却由第三方实现,这样就存在一个问题:SPI 的接口是 Java
核心库的一部分,是由BootstrapClassLoader加载的;SPI实现的Java类一般是由AppClassLoader来加载的。BootstrapClassLoader是无法找到 SPI 的实现类的,因为它只加载Java的核心库。它也不能代理给AppClassLoader,因为它是最顶层的类加载器。也就是说,双亲委派模型并不能解决这个问题。

双亲委派模式的好处

第一,Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层次关系可以避免类的重复加载,当父类加载器已经加载过一次时,没有必要子类再去加载一次。
第二,考虑到安全因素,Java 核心 Api 类不会被随意替换,核心类永远是被上层的类加载器加载。如果我们自己定义了一个 java.lang.String 类,它会优先委派给 BootStrapClassLoader 去加载,加载完了就直接返回了。
如果我们定义了一个 java.lang.ExtString,能被加载吗?答案也是不能的,因为 java.lang 包是有权限控制的,自定义了这个包,会报一个错如下:

java.lang.SecurityException: Prohibited package name: java.lang

SPI类无法加载?

如果不做任何的设置,Java应用的线程的上下文类加载器默认就是AppClassLoader。在核心类库使用SPI接口时,传递的类加载器使用线程上下文类加载器,线程上下文加载器,来打破双亲委派模型。即父类使用子类的类加载器来进行类加载。从而保证父子类由一个类加载器进行加载,可以成功的加载到SPI实现的类。线程上下文类加载器在很多SPI的实现中都会用到。
通常我们可以通过Thread.currentThread().getClassLoader()和Thread.currentThread().getContextClassLoader()获取线程上下文类加载器。
这里的上下文类加载器(ContextClassLoader ),它其实是破坏了双亲委派机制的,但是也为程序带来了巨大的灵活性和可扩展性。

其实 ServiceLoader 核心的逻辑就在这两个方法里

private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    // 寻找 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;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                // 加载这个类
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                // 初始化这个类
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

寻找 META-INF/services/类,解析类的内容,构造 Class ,初始化,返回,就这么简单了。

4.SPI的简单实现

下面我们来简单实现一个jdk的SPI的简单实现。

首先第一步,定义一组接口:

1 public interface UploadCDN {
2     void upload(String url);
3 }

这个接口分别有两个实现:

public class QiyiCDN implements UploadCDN {  //上传爱奇艺cdn
    @Override
    public void upload(String url) {
        System.out.println("upload to qiyi cdn");
    }
}
 
public class ChinaNetCDN implements UploadCDN {//上传网宿cdn
    @Override
    public void upload(String url) {
        System.out.println("upload to chinaNet cdn");
    }
}
然后需要在resources目录下新建META-INF/services目录,并且在这个目录下新建一个与上述接口的全限定名一致的文件,在这个文件中写入接口的实现类的全限定名:

在这里插入图片描述
在这里插入图片描述
这时,通过serviceLoader加载实现类并调用:

public static void main(String[] args) {
        ServiceLoader<UploadCDN> uploadCDN = ServiceLoader.load(UploadCDN.class);
        for (UploadCDN u : uploadCDN) {
            u.upload("filePath");
        }
    }

输出如下:
在这里插入图片描述
这样一个简单的spi的demo就完成了。可以看到其中最为核心的就是通过ServiceLoader这个类来加载具体的实现类的。

5. SPI原理解析

 通过上面简单的demo,可以看到最关键的实现就是ServiceLoader这个类,可以看下这个类的源码,如下:
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;
 
    // 上下文对象
    private final AccessControlContext acc;
 
    // 按照实例化的顺序缓存已经实例化的类
    private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
 
    // 懒查找迭代器
    private java.util.ServiceLoader.LazyIterator lookupIterator;
 
    // 私有内部类,提供对所有的service的类的加载与实例化
    private class LazyIterator implements Iterator<S> {
        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        String nextName = null;
 
        //...
        private boolean hasNextService() {
            if (configs == null) {
                try {
                    //获取目录下所有的类
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    //...
                }
                //....
            }
        }
 
        private S nextService() {
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //反射加载类
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
            }
            try {
                //实例化
                S p = service.cast(c.newInstance());
                //放进缓存
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                //..
            }
            //..
        }
    }
}
 上面的代码只贴出了部分关键的实现,有兴趣的读者可以自己去研究,下面贴出比较直观的spi加载的主要流程供参考:

在这里插入图片描述

6.SpringBoot SPI

在Spring中提供了SPI机制,我们只需要在 META-INF/spring.factories 中配置接口实现类名,即可通过服务发现机制,在运行时加载接口的实现类:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration

在 spring-boot-autoconfigure 模块下,SpringBoot默认就配置了很多接口的服务实现:
其实我们在使用三方 spring-boot-starter就是使用这种机制,我们的Application显然不会和三方jar处于同包或者子包。三方Bean Configuration需要加载就需要使用spring 的spi机制。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/92522.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据Kudu(七):Kudu分区策略

文章目录 Kudu分区策略 一、​​​​​​​Partition By Range - 范围分区

最通俗易懂的 JAVA slf4j,log4j,log4j2,logback 关系与区别以及完整集成案例

最近在工作中&#xff0c;发现接触到的很多小伙伴分不清楚logback slf4j 以及log4j 的关系&#xff0c;有的人认为是一个东西&#xff0c;有的人认为是完全没关系&#xff0c;或者说有关系但是不清楚具体是什么区别和联系&#xff0c;今天咱们就简单梳理下他们之间的联系和区别…

项目式学习法(PBL)如何让你快速成为行业专家【一杯咖啡谈项目】

项目人人都是主角&#xff0c;没有旁观者。我们每个人也应当好PM&#xff0c;这就离不开学习提升自己&#xff0c;&#xff0c;如此&#xff0c;方能更好推动经济社会高质量发展。 1、项目式学习是什么&#xff1f; 关于项目式学习&#xff0c;目前国内外还没有个统一的定义&…

【python】 json字符串转对象

目录 一&#xff1a;json对象转换为json字符串 二&#xff1a;json字符串转换为json对象 三&#xff1a;json字符串{"name":"lily","sno":1001} 四&#xff1a;python面向对象程序设计 一&#xff1a;json对象转换为json字符串 import json…

Elasticsearch 安装及启动【Linux】

一、下载安装包 1.下载 Elasticsearch 官网下载地址&#xff1a;https://www.elastic.co/cn/downloads/past-releases#elasticsearch 2.下载 Kibana Kibana 数据可视化平台可以选择性安装 官网下载地址&#xff1a;https://www.elastic.co/cn/downloads/past-releases#kiban…

连续仨月霸占牛客榜首京东T8呕心巨作:700页JVM虚拟机实战手册

什么是Java虚拟机&#xff1f; 虚拟机是一种抽象化的计算机&#xff0c;通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架构&#xff0c;如处理器、堆栈、寄存器等&#xff0c;还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息…

怎么防止同事用Evil.js的代码投毒

最近Evil.js被讨论的很多&#xff0c;项目介绍如下 项目被发布到npm上后&#xff0c;引起了激烈的讨论&#xff0c;最终因为安全问题被npm官方移除&#xff0c;代码也闭源了 作为一个前端老司机&#xff0c;我肯定是反对这种行为&#xff0c;泄私愤有很多种方式&#xff0c;代…

深度学习——多GPU训练代码实现

1.数据并行性。 一台机器有K个GPU&#xff0c;给定训练模型&#xff0c;每个GPU参数值是相同且同步&#xff0c;每个GPU独立地维护一组完整地模型参数。k2时数据并行地训练模型&#xff0c;利用两个GPU上的数据&#xff0c;并行计算小批量随机梯度下降。 K个GPU并行训练过程&a…

【Spring Cloud】Eureka注册中心从原理到实战图文详细教程

本期目录1. Eureka介绍1.1 Eureka能解决的问题2. Eureka原理3. 搭建Eureka Server3.1 引入依赖3.2 编写启动类3.3 修改配置文件3.4 启动Eureka微服务4. 服务注册4.1 导入依赖4.2 修改配置文件4.3 重启微服务4.4 启动多个微服务实例5. 服务发现5.1 修改业务层代码5.2 在RestTemp…

A_A05_002 sscom33串口调试助手使用

目录 一、软件获取 二、软件基本功能介绍 1、接收区 2、串口通信参数配置区 3、串口打开关闭与其他设置区域 4、手动发送区域 5、多文本发送区 6、辅助区域 三、注意事项 一、软件获取 网盘链接 直戳跳转 二、软件基本功能介绍 1、接收区 接收区就是接收外部设备给串口…

【2022年终总结】总结自己的2022,展望2023

目录一、工作杭州【述职-涨薪】【项目】从0到1&#xff0c;从1到多&#xff0c;在工作中寻找方法&#xff0c;承担更多的责任【技能】丰富了技术广度武汉1. 【项目】一个人就是一个团队二、成为博客新秀&前端领域优质创作者三、生活旅行猫猫情感2023年的flag学习&#xff1…

FFmpeg- 常用的滤镜命令

下面来熟悉一下常用的对视频操作的几个命令。这次需要完成的命令也包括在其中。 视频的画面大小的剪切(crop filter) 将输入的视频的帧&#xff0c;以左上角为坐标的原点&#xff0c;剪切成x,y坐标开始的指定大小。 语法&#xff1a; # []包裹的选项是可选的 crop ow[:oh[:x[:…

3D三维地图APP

3D三维地图APP 发布时间&#xff1a;2018-07-19 版权&#xff1a; 3D地图依据高程数据等对地表进行渲染&#xff0c;实现地表的起伏&#xff0c;模拟出真实的三维场景&#xff0c;让你有如身临其境般的感觉。 &#xff08;注&#xff1a;Bigemap 3D地图是一个三维地图浏览功能…

RNA-seq 详细教程:Wald test(10)

学习目标 了解生成比较结果所需的步骤&#xff08;Wald 检验&#xff09;总结不同层次的基因过滤了解对数倍变化收缩结果探索 默认情况下&#xff0c;DESeq2 使用 Wald 检验来识别在两个样本之间差异表达的基因。给定设计公式中使用的因素&#xff0c;以及存在多少个因素水平&a…

大学生影视主题网页制作 腾龙电影网页设计模板 学生静态网页作业成品 dreamweaver电影HTML网站制作

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 文章目录一、网页介绍一…

基于WIFI无线组网的水雨情远程监测预警系统

水雨情是重要的水文资料&#xff0c;在水资源配置和管理中有重要的参考价值&#xff0c;具体是指水位、流速、流量、降雨量、降雨强度等参数。随着物联网、雷达遥测、无线通信技术的发展&#xff0c;这些数据都能实现自动感知和远程监测&#xff0c;对于防汛抗洪和日常巡检有重…

MySql explain

执行计划是SQL语句经过查询分析器后得到的 抽象语法树 和 相关表的统计信息 作出的一个查询方案&#xff0c;这个方案是由查询优化器自动分析产生的。由于是动态数据采样统计分析出来的结果&#xff0c;所以可能会存在分析错误的情况&#xff0c;也就是存在执行计划并不是最优的…

经CSDN副总裁点拨,我发现了世界杯球队与优秀开发团队的共通点

☆ 世界杯已经快要接近尾声了&#xff0c;而无论法国还是阿根廷谁能走到最后&#xff0c;无疑他们都是非常优秀的世界杯球队&#xff0c;甚至可以说&#xff0c;能进入世界杯的球队&#xff0c;都是举世瞩目的国家队阵容。 ☆ 而我最近也一直在思考&#xff0c;那么我们的开发团…

SpringBoot2核心技术(核心功能)- 05、Web开发【5.1 SpringMVC自动配置概览+5.2简单功能分析】

1、SpringMVC自动配置概览 Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置) The auto-configuration adds the following features on top of Spring’s defaults: ● Inclusion of ContentN…

手把手教你实现一个function模板

1.实现function需要用到的相关技术 建议看本文之前&#xff0c;需要先了解C11 function或者boost::function模板的基本用法&#xff0c;也最好看一下我的另外一篇文章&#xff1a; c11 function模板&#xff1a;模板特化与可变参数函数模板 如果你使用过C11 function模板或者…