Java SPI机制详解

Java SPI机制详解

1、什么是SPI?

   SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制, 比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。

在这里插入图片描述

​ 如上图所示,接口对应的抽象SPI接口;实现方实现SPI接口;调用方依赖SPI接口。

​ SPI接口的定义在调用方,在概念上更依赖调用方;组织上位于调用方所在的包中,实现位于独立的包中。

​ 当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务实现的工具类是:java.util.ServiceLoader。

2、SPI的用途

​ 数据库DriverManager、Spring、ConfigurableBeanFactory等都用到了SPI机制,这里以数据库DriverManager为例,看一下其实现的内幕。

​ DriverManager是jdbc里管理和注册不同数据库driver的工具类。针对一个数据库,可能会存在着不同的数据库驱动实现。我们在使用特定的驱动实现时,不希望修改现有的代码,而希望通过一个简单的配置就可以达到效果。 在使用mysql驱动的时候,会有一个疑问,DriverManager是怎么获得某确定驱动类的?我们在运用Class.forName(“com.mysql.jdbc.Driver”)加载mysql驱动后,就会执行其中的静态代码把driver注册到DriverManager中,以便后续的使用。

Driver实现
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!");}}
}

驱动的类的静态代码块中,调用DriverManager的注册驱动方法new一个自己当参数传给驱动管理器。

Mysql DriverManager实现
    /*** Load the initial JDBC drivers by checking the System property* jdbc.properties and then use the {@code ServiceLoader} mechanism*/static {loadInitialDrivers();println("JDBC DriverManager initialized");}

可以看到其内部的静态代码块中有一个loadInitialDrivers方法,loadInitialDrivers用法用到了上文提到的spi工具类ServiceLoader:

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;}// If the driver is packaged as a Service Provider, load it.// Get all the drivers through the classloader// exposed as a java.sql.Driver.class service.// ServiceLoader.load() replaces the sun.misc.Providers()AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();/* Load these drivers, so that they can be instantiated.* It may be the case that the driver class may not be there* i.e. there may be a packaged driver with the service class* as implementation of java.sql.Driver but the actual class* may be missing. In that case a java.util.ServiceConfigurationError* will be thrown at runtime by the VM trying to locate* and load the service.** Adding a try catch block to catch those runtime errors* if driver not available in classpath but it's* packaged as service and that service is there in classpath.*/try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});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);}}

先查找jdbc.drivers属性的值,然后通过SPI机制查找驱动

public final class ServiceLoader<S>implements Iterable<S>
{private static final String PREFIX = "META-INF/services/";
private boolean hasNextService() {if (nextName != null) {return true;}if (configs == null) {try {String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = 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;}

可以看到加载META-INF/services/ 文件夹下类名为文件名(这里相当于Driver.class.getName())的资源,然后将其加载到虚拟机。

注释有这么一句“Load these drivers, so that they can be instantiated.” 意思是加载SPI扫描到的驱动来触发他们的初始化。即触发他们的static代码块

/*** Registers the given driver with the {@code DriverManager}.* A newly-loaded driver class should call* the method {@code registerDriver} to make itself* known to the {@code DriverManager}. If the driver is currently* registered, no action is taken.** @param driver the new JDBC Driver that is to be registered with the*               {@code DriverManager}* @param da     the {@code DriverAction} implementation to be used when*               {@code DriverManager#deregisterDriver} is called* @exception SQLException if a database access error occurs* @exception NullPointerException if {@code driver} is null* @since 1.8*/public static synchronized void registerDriver(java.sql.Driver driver,DriverAction da)throws SQLException {/* Register the driver if it has not already been added to our list */if(driver != null) {registeredDrivers.addIfAbsent(new DriverInfo(driver, da));} else {// This is for compatibility with the original DriverManagerthrow new NullPointerException();}println("registerDriver: " + driver);}

将自己注册到驱动管理器的驱动列表中

public class DriverManager {// List of registered JDBC driversprivate final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

当获取连接的时候调用驱动管理器的连接方法从列表中获取。

  @CallerSensitivepublic static Connection getConnection(String url,String user, String password) throws SQLException {java.util.Properties info = new java.util.Properties();if (user != null) {info.put("user", user);}if (password != null) {info.put("password", password);}return (getConnection(url, info, Reflection.getCallerClass()));}
 private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {/** When callerCl is null, we should check the application's* (which is invoking this class indirectly)* classloader, so that the JDBC driver class outside rt.jar* can be loaded from here.*/ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;synchronized(DriverManager.class) {// synchronize loading of the correct classloader.if (callerCL == null) {callerCL = Thread.currentThread().getContextClassLoader();}}if(url == null) {throw new SQLException("The url cannot be null", "08001");}println("DriverManager.getConnection(\"" + url + "\")");// Walk through the loaded registeredDrivers attempting to make a connection.// Remember the first exception that gets raised so we can reraise it.SQLException reason = null;for(DriverInfo aDriver : registeredDrivers) {// If the caller does not have permission to load the driver then// skip it.if(isDriverAllowed(aDriver.driver, callerCL)) {try {println("    trying " + aDriver.driver.getClass().getName());Connection con = aDriver.driver.connect(url, info);if (con != null) {// Success!println("getConnection returning " + aDriver.driver.getClass().getName());return (con);}} catch (SQLException ex) {if (reason == null) {reason = ex;}}} else {println("    skipping: " + aDriver.getClass().getName());}}// if we got here nobody could connect.if (reason != null)    {println("getConnection failed: " + reason);throw reason;}println("getConnection: no suitable driver found for "+ url);throw new SQLException("No suitable driver found for "+ url, "08001");}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {boolean result = false;if(driver != null) {Class<?> aClass = null;try {aClass =  Class.forName(driver.getClass().getName(), true, classLoader);} catch (Exception ex) {result = false;}result = ( aClass == driver.getClass() ) ? true : false;}return result;}

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

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

相关文章

B端:导航条长得不都一样吗?错了,这里看过来就懂了。

B端导航条看似都一样&#xff0c;大差不差&#xff0c;仔细看一下&#xff0c;其实各有各的不同&#xff0c;这里方向了十多个&#xff0c;大家仔细看细节。

avicat连接异常,错误编号2059-authentication plugin…

错误原因为密码方式不对&#xff0c;具体可自行百度 首先管理员执行cmd进入 mysql安装目录 bin下边 我的是C:\Program Files\MySQL\MySQL Server 8.2\bin> 执行 mysql -u -root -p 然后输入密码 123456 进入mysql数据库 use mysql 执行 ALTER USER rootlocalhost IDE…

关于沃进科技无线模块demo软件移植问题

文章目录 一、无线模块开发测试准备二、开发板硬件三、开发板默认功能上电默认界面功能选择界面数据包发送界面数据包接收显示界面射频性能测试界面参数设置界面固件信息显示界面 四、软件开发软件SDK框图1、射频硬件驱动&#xff08;详见./radio/myRadio_gpio.c&#xff09;2、…

51单片机实验04 -数码管的动态显示实验

目录 一、实验目的 二、实验内容 三、实验原理 四、实验方法 五&#xff0c;实验效果及代码 1&#xff0c;效果 2&#xff0c;代码 六&#xff0c;课后习题 1&#xff0c;使用定时器T0的中断函数1 从999999~0计时 1&#xff09;效果 2&#xff09;代码 2&#xff0c…

配置linux的oracle 21c启停服务

一、配置启停 1、使用root用户登陆 su - root 2、修改oratab文件 修改oratab文件&#xff0c;将红框里面的N改为“Y”&#xff0c;使启停脚本能够生效 vi /etc/oratab 3、验证 配置好后就能够使用 dbshut 停止服务 和 dbstart 启动服务 了 2.1启动服务 su - oracle dbstart…

什么是线程?线程和进程谁更弔?

第一个参数是所创建进程的pid。 第二个是线程的属性。 第三个参数是返回值为void*&#xff0c;参数也为void*的函数指针。 第四个参数是给第三个参数的参数&#xff0c;也就是给给函数传参。 #include<iostream> #include<pthread.h> #include<unistd.h>…

折叠面板组件(vue)

代码 <template><div class"collapse-info"><div class"collapse-title"><div class"title-left">{{ title }}</div><div click"changeHide"> <Button size"small" v-if"sho…

生产计划和排单管理怎么做

阅读本文&#xff0c;你将了解到&#xff1a;1、企业是如何制定生产计划和进行排单管理&#xff1f; 2.企业在执行生产计划和进行排单管理过程中会遇到那些问题&#xff1f; 3.企业如何高效利用工具去解决问题&#xff1f; 一、生产计划和排单管理是什么 1.生产计划和排单管理…

【uniapp】【uview2.0】【u-sticky】Sticky 吸顶

把pages.json文件中的 “navigationStyle"设置为"custom”, 出现的问题是&#xff0c;莫名奇妙多了个 一个高度 解决方法 /* 使用CSS的sticky定位 */ .sticky {/* #ifdef H5 */ position: -webkit-sticky;position: sticky;top: 0; /* 设置距顶部的距离 */z-ind…

[Python开发问题] Selenium ERROR: Unable to find a matching set of capabilities

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

在Spring Boot实战中碰到的拦截器与过滤器是什么?

在Spring Boot实战中&#xff0c;拦截器&#xff08;Interceptors&#xff09;和过滤器&#xff08;Filters&#xff09;是两个常用的概念&#xff0c;它们用于在应用程序中实现一些通用的逻辑&#xff0c;如日志记录、权限验证、请求参数处理等。虽然它们都可以用于对请求进行…

最大子树和(遇到的题)

题目是给出一个树&#xff0c;求其中最大的权值块 题解&#xff1a; #include <bits/stdc.h> using namespace std; const int N1e59; int dp[N]; //dp[i]表示第i结点为根最大权值 int w[N]; //记录每个结点的权值 int n; //点的数量 int t; //样例个数 …

Ubuntu安装VMVare Workstation pro 17.5.1

由于需要装Kali&#xff0c;我电脑是Ubuntu单系统&#xff0c;所以只能使用linux版本的虚拟机&#xff0c;通过这种方式来安装虚拟机和Kali镜像。 参考CSDN博客资料&#xff1a;https://blog.csdn.net/xiaochong0302/article/details/127420124 github代码资料&#xff1a;vm…

程序运行在 STM32H750 的外扩 FLASH 上两小时后死机

1. 问题现象 客户使用 STM32H750VBT6&#xff0c;通过 QSPI 外扩了一个 4M 的 NOR FLASH&#xff0c;采用memory map 模式。当程序跳转运行到外设 FLASH 后&#xff0c;大约两个小时后程序死机。 客户使用的 IDE 是 KEIL&#xff0c;此问题可以固定重现。 在 KEIL 调试模式下…

百货商场用户画像描绘and价值分析(下)

目录 内容概述数据说明技术点主要内容4 会员用户画像和特征字段创造4.1 构建会员用户基本特征标签4.2 会员用户词云分析 5 会员用户细分和营销方案制定5.1 会员用户的聚类分析及可视化5.2 对会员用户进行精细划分并分析不同群体带来的价值差异 内容概述 本项目内容主要是基于P…

Springboot+Vue项目-基于Java+MySQL的免税商品优选购物商城系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

【Qt】常用控件(LCD Number/进度条/日历)

需要云服务器等云产品来学习Linux可以移步/-->腾讯云<--/官网&#xff0c;轻量型云服务器低至112元/年&#xff0c;新用户首次下单享超低折扣。 目录 一、LCD Number(LCD显示器) 一个倒计时程序 二、ProgressBar(进度条) 1、创建一个进度条&#xff0c;100ms进度增加…

✌粤嵌—2024/3/14—判断子序列

代码实现&#xff1a; 方法一&#xff1a;一次遍历 bool isSubsequence(char *s, char *t) {if (strlen(s) 0) {return true;}int i 0;for (int j 0; j < strlen(t); j) {if (s[i] t[j]) {i;}if (i strlen(s)) {return true;}}return false; } 方法二&#xff1a;动态规…

5.2 mybatis之autoMappingBehavior作用

文章目录 1. NONE关闭自动映射2. PARTIAL非嵌套结果映射3. FULL全自动映射 众所周知mybatis中标签< resultMap >是用来处理数据库库字段与java对象属性映射的。通常java对象属性&#xff08;驼峰格式&#xff09;与数据库表字段&#xff08;下划线形式&#xff09;是一 一…

基于SSM项目个人健康信息管理系统

采用技术 基于SSM项目个人健康信息管理系统的设计与实现~ 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringMVCMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 页面展示效果 用户端 用户首页 健康知识 用户注册 医院推荐信息 系统概要…