java的SPI机制(jdk1.8)

SLF4J版本冲突:从No Providers到StaticLoggerBinder的全面解析 本文深入解析了SLF4J日志框架中常见的“No SLF4J providers were found”和“StaticLoggerBinder”错误。核心原因在于SLF4J 1.8.0版本前后绑定机制的重大变化,导致API版本与具体日志实现版本不匹配。文章提供了清晰的诊断步骤,并针对JDK 8及以下和JDK 9+两种环境,分别给出了降级兼容与升级拥抱SPI机制的实战解决方案。 阅读详情

SPI全名Service Provider Interface(服务提供者接口),SPI的主要目的是实现服务的热插拔效果,主要应对的场景是设计者提供一个接口,这个接口的具体实现由不同厂商提供,设计者只要引入厂商提供的实现jar包就可以自动装配,无需改动代码。例如:JDBC的驱动加载。SPI机制满足:

  • 不需要改动源码就可以实现扩展,解耦。
  • 实现扩展对原来的代码几乎没有侵入性。
  • 只需要添加配置就可以实现扩展,符合开闭原则。
    其实很多知名框架的自动装配都是借鉴了JDK的SPI机制如SpringBoot的starter、dubbo的spi扩展等。
  1. 接下来就借助JDBC驱动装置这个经典例子来讲解spi机制。
 		// 加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 获取连接
        Connection connection = DriverManager.getConnection(url, user, paw);
        // 创建preparedStatement
        // ..省略

JDBC的连接数据库的步骤一般如上段代码所示,由于在第一行代码中显示的加载了mysql驱动这里体现不出spi机制的存在,试问如果我们去掉Class.forName(“com.mysql.jdbc.Driver”);这段代码,当我们获取连接时系统自动去加载数据库驱动可不可行,答案是可行的。那问题来了系统是怎么知道mysql驱动的实现类是com.mysql.jdbc.Driver并且加载他的呢?

DriverManager中有个静态代码块,在DriverManager初始化时会被调用

 static {
        // 基于SPI机制加载数据库驱动
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }




private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }

        // 根据spi机制装配驱动
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
			// 创建ServiceLoader
           ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                // 获取驱动实现迭代器
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                try{
              		// 遍历加载全部驱动实现
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        // spi机制未装配到驱动,获取系统参数中的驱动进行装配
        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }
  1. 创建ServiceLoader服务加载器,在内部设置类加载器为线程上下文类加载器(这个很关键,由于ServiceLoader是由系统类加载器加载的类,所以无法加载classpath下的资源和驱动实现类所以需要打破双亲委派机制借助线程上下文类加载器加载)
 public static <S> ServiceLoader<S> load(Class<S> service) {
        // 获取线程上下文类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }


    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }


private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        // 如果线程上下文类加载器是空,就取系统类加载器
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
 }
  1. 1中遍历全部驱动调用了driversIterator.hasNext(),最终会调用ServiceLoader$LazyIterator.hasNextService()方法,此时会在会获取META-INF/services/java.sql.Driver下的驱动实现类全限定名。
private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    // 拼接为 META-INF/services/java.sql.Driver
                    String fullName = PREFIX + service.getName();
                    // 如果线程上下文类加载器为null则用系统类加载器,如果不为null则用线程上下文类加载器
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            // 解析classpath下面的所有的META-INF/services/java.sql.Driver文件获取实现类全限定名
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                // 解析核心方法
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

每个厂商的驱动jar都在META-INF/services/java.sql.Driver里存放驱动的实现类
在这里插入图片描述

  1. 1中遍历驱动调用了driversIterator.next();加载驱动。
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 {
                // 强转成Driver类型
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated: " + x,
                     x);
            }
            throw new Error();          // This cannot happen
        }
  1. com.mysql.jdbc.Driver中有静态方法调用DriverManager.registerDriver()方法将自身注入到DriverManager中用于后续连接获取。
package com.mysql.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

后面获取连接的细节就不多说了,有兴趣的可以自己去撸源码,撸源码的时候也着重关注下ServiceLoader类这个类很多小细节。
总结下SPI实现服务热拔插的关键:

  • 约定固定目录META-INF/services/+接口名文件存放接口实现类全限定名
  • 打破双亲委派机制使用线程上下文类加载器来加载classpath下面的供应商服务实现(如果线程上下文类加载器设置为自定义的类加载器则可以从数据库、网络等多种途径装配供应商的服务实现)
SLF4J版本冲突:如何优雅解决No SLF4J providers were found错误 本文深入解析了Java开发中常见的“No SLF4J providers were found”错误,该问题常由SLF4J API与日志实现绑定器版本不匹配导致。文章从错误根源入手,对比了SLF4J 1.7.x与1.8.0+版本在JDK 8及JDK 9+环境下的不同服务发现机制,并提供了从版本统一、依赖管理到桥接模块使用的四种实战解决方案,帮助开发者一劳永逸地修复日志系统失效问题。 阅读详情

相关推荐

Python爬虫实战:5分钟搞定东方财富网股票数据抓取(附完整代码)

本文详细介绍了如何利用Python爬虫技术,快速抓取东方财富网的实时股票数据。通过逆向分析其JSONP数据接口,构建了包含请求处理、数据清洗、分页调度及多格式存储的完整自动化采集系统,并提供了应对反爬策略与生产部署的进阶优化思路,附有可直接运行的完整代码。

weixin_29084231的博客 870

java6新特性----SPI机制

SPI: 全称是**ServiceProviderInterface**它是从Java 6 开始引入的,是一种基于 ClassLoader 来发现并加载服务的机制。一种标准的SPI,由三个组件构成,分别是:ServiceService,是一个公开的接口或抽象类,定义了一个抽象的功能模块Service Provider,则是Service接口的一个实现类。ServiceLoader, 是SPI机制中的核心组件,负责在运行时发现并加载 Service Provider。

weixin_44235759的博客 524

从IPv6到Tomcat:彻底解决127.0.0.1拒绝连接的完整指南

本文提供了从IPv6到Tomcat的全面指南,帮助开发者彻底解决127.0.0.1拒绝连接的问题。涵盖网络协议栈、服务配置、端口管理等多个层面的排查方法,特别针对Tomcat服务提供了专项解决方案,确保开发者能够快速定位并修复连接问题。

weixin_29207439的博客 471

Java8:SPI机制

SPI(Service Provider Interface)是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用。比如java.sql.Driver接口,不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦。

CRUD工程师 880

【全网最新】使用 1panel面板 部署若依系统( springboot 和 vue)项目

打开前端项目的vue.config.js文件,检查devServer下的proxy代理部分,然后将下面这个端口修改为你要在打包后访问的端口,即使用nginx代理前的接口。这个端口默认是80端口,这里把其修改为不会冲突的8010端口,后台接口的地址配置为9080,不过感觉这里设置的8010和9080可能没有用,因为部署前端静态页面是,也配置了一次网页的外部接口和api的接口,后期有空了再验证。打开右侧的Maven,点击:生存期->package,然后点击运行,等待打包成功,包在如图所示的文件夹中。

老薛的专栏 1754

浅析ArcGis中的软件——ArcMap、ArcScene、 ArcGlobe、ArcCatalog

ArcMap:是集空间数据显示、矢量数据编辑、冲突数据检查(拓扑检查)、查询检索(地理编码)、统计(字段属性表)、报表生成、空间分析(ArcGIS Spatial Analyst)和高级制图(制图表达)等众多功能干一体的桌面应用平台软件。而且提供所见即所得的符号编辑器,令用户可以随心所欲地生成任意复杂的点、线、面符号。所支持的空间数据格式包括Arclnfo coverages、ESRI shapefiles、ArcSDE lavers、DXF、DWG、DGN、常见的图像格式、GRIDs、TINs等。

lxlmycsdnfree的博客 6562

详解为什么Jdk旧版本加载jdbc驱动需要Class.forName 新版本的的SPI机制怎么实现

详解为什么Jdk旧版本加载jdbc驱动需要Class.forName 新版本的的SPI机制怎么实现

weixin_43488886的博客 560

解决DevExpress汉化后不生效的5个常见问题及修复方法

本文详细解析了DevExpress汉化后不生效的5个常见问题及修复方法,包括汉化包部署位置验证、运行时缓存清除、权限与执行上下文排查、版本兼容性检测以及开发环境配置检查。通过实战案例和具体操作步骤,帮助开发者快速解决汉化失效问题,提升开发效率。

weixin_29232121的博客 261

Java -- JDK中SPI机制

(),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件Java SPI是一种以接口为基础,使用配置文件来加载(或称之为服务发现)的动态加载机制,主要使用JDK中来实现​SPI是一种动态替换发现的机制,比如有个接口,想运行时动态的给它添加实现,只需要添加一个实现。

MinggeQingchun的博客 2447

数据处理和分析之数据聚类:层次聚类(Hierarchical Clustering):单链接层次聚类方法

单链接层次聚类(Single-Linkage Hierarchical Clustering)是凝聚型层次聚类的一种,它基于数据点之间的最小距离来定义聚类间的相似性。在单链接层次聚类中,两个聚类之间的距离定义为这两个聚类中任意两个数据点之间的最短距离。这种聚类方法倾向于形成链状或线性的聚类,因为聚类的边界是由最远的数据点决定的,而不是聚类的平均或中心位置。单链接层次聚类通过计算簇间最近点的距离来决定簇的合并,这种方法简单直观,但可能导致簇的形状不规则,尤其是在数据点分布不均匀的情况下。

2401_87715305的博客 1441

PyQt(Python+Qt)学习随笔:PyQt界面派生类构造方法中super方法的使用

老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 在《第15.7节 PyQt入门学习:PyQt5应用构建详细过程介绍》和订阅专栏《第二章、PyQt5应用构建详细过程介绍》中,老猿介绍界面派生类的构造方法如下: class MediaPlayerWin(QtWidgets.QWidget,Ui_UI_PlayerWin): def __i...

老猿Python 2711

构建JDK8报错了,哪位大神能帮忙看下什么原因 ?

构建JDK8报错了,哪位大神能帮忙看下什么原因 ?

WoZhiShuoLiangJuHua的博客 3582

微信小程序 - 最新详细实现气泡菜单功能,点击任意元素在底部或附近周围弹出气泡菜单列表,仿微信气泡弹框显示菜单快捷功能收纳,自定义气泡弹窗/菜单气泡弹窗,小程序气泡框嵌套菜单组件插件,自动计算弹出定位

微信小程序,vue2,vue3,气泡列表,汽泡菜单,气泡框,泡泡框,悬浮框,悬浮菜单,点击菜单按钮弹出气泡,对话箭头气泡框,弹出的气泡准确定位并吸附在附近,弹出式气泡,弹窗菜单,气泡自定义HTML弹窗,带三角的气泡弹框,三角气泡框里面嵌套菜单,当点击一个按钮或元素时,会从附近弹出一个小气泡一样的菜单列表,自定义气泡框内的内容,点击一个元素在附近弹出一个气泡弹框功能效果,气泡弹框不准怎么办,被盖住了,挡住了,长按弹出,组件插件,微信,自定义气泡菜单组件,气泡弹出框,小程序自定义气泡框,菜单气泡,包裹列表的三角

🏆 前端领域优质创作者 3915

深入理解jvm(五)之类加载机制

深入理解类加载机制是非常重要的,这可以帮助我们更好地设计和开发Java应用程序,并在面对复杂的类加载问题时更加从容应对。我们需要不断的学习,不断地积累,理论知识是技术落地的基础,技术实现是对理论知识的检验!

清晨qc的博客 224

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Java SPI(Service Provider Interface)简介

SPI 简介SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。一个服务(Service)通常指的是已知的接口或者抽象类,服务提供方就是对这个接口或者抽象类的实现,然后按照SPI 标准存放到资源路径META-INF/services目录下,文件的命名为该服务接口的全限定名。如有一个服务接口:package com.ricky.codelab

Ricky 1万+

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

Java基础面试题总结《三》

SPI全称是(服务提供者接口)。它是一种基于接口的动态扩展机制。简单来说,就是 Java 定义了一套标准接口,但具体的实现交给第三方(开发者或厂商)去完成。系统运行时,会自动发现并加载这些实现类,而不需要硬编码在代码里。目的:确保逻辑相等的对象拥有相同的哈希码,从而能被正确地存入和取出哈希集合。原则只要参与了哈希运算(作为 Key 或集合元素),就必须同时重写这两个方法。策略模式和观察者模式都属于行为型设计模式。

qq_55862497的博客 259

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

【一文读懂】SPI机制之JDK的SPI实现详解

Java SPI实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,核心目的是解耦。 SPI符合程序设计的开闭原则 PS:开闭原则(Open Close Principle),简称OCP,是面向对象设计中重要的原则,它要求软件实体对扩展开放,对修改封闭,软件实体包含函数、类、模块甚至是可执行程序。

desire1992的博客 1935

【JDK源码】Java中的SPI机制

JDK源码深入分析,SPI的机制

youngjre的博客 1083

Java的SPI机制

SPI是什么 SPI的英文名称是Service Provider Interface,是Java 内置的服务发现机制。 在开发过程中,将问题进抽象成API,可以为API提供各种实现。如果现在需要对API提供一种新的实现,我们可以不用修改原来的代码,直接生成新的Jar包,在包里提供API的新实现。通过Java的SPI机制,可以实现了框架的动态扩展,让第三方的实现能像插件一样嵌入到系统中。 Java的...

小白张的博客 1万+

聊聊JAVA的SPI机制

介绍了JDK SPI的原理以及实战案例,Spring SPI非自动注入和自动注入的区别与原理

github_33882640的博客 2115

Java常用机制 - SPI机制详解

SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦。

珍惜当下 3503

JDK和Spring的SPI机制原理分析

Java SPI(Service Provider Interface)是 Java 提供的一种轻量级的服务发现机制。它可以让开发者通过约定的方式,在程序运行时动态地加载和替换接口的实现,从而提高程序的扩展性和灵活性Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制要使用Java SPI,需要遵循如下约定:当服务提供者提供了接口的一种具体实现后,在jar包的目录下创建一个以接口全限定名为命名的文件,内容为实现类全限定名接口实现类所在的jar包放在主程序的。

weixin_51918722的博客 1220

Java SPI机制

SPI机制简介 SPI的全名为Service Provider Interface.java spi机制的思想: 系统里抽象的各个模块,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现...

一起进步的博客 1万+

java实践SPI机制及浅析源码

1.概念 正式步入今天的核心内容之前,溪源先给大家介绍一下关于SPI机制的相关概念。 SPI即Service Provider Interface,属于JDK内置的一种动态的服务提供发现机制,可以理解为运行时动态加载接口的实现类。更甚至,大家可以将SPI机制与设计模式中的策略模式建立联系。 SPI机制: 从上图中理解SPI机制:标准化接口+策略模式+配置文件; SPI机制核心思想:系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬

增量星球 4082

Jdk、Spring、Dubbo之SPI机制

SPI简介 SPI全称是Service Provider Interface是一种自动服务发现注册机制。本质是将接口实现类的全限定名配置在约定的配置文件,然后由服务加载器读取指定的加载文件名称,再动态加载实现类。这样我们可以在运行的时候动态替换接口实现,通过spi机制可以轻松实现我们应用程序的拓展功能。 那么spi到底有什么用?在哪里用到了呢?下面将会以 JavaSPI机制 SpringSPI机制 SpringBootSPI机制 ...

大张 2200
上一篇: 类加载器一篇足以
下一篇: 程序计数器
躺平程序猿
博客等级 码龄8年 4870粉丝 71原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

躺平程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值