【Web】浅聊JDBC的SPI机制是怎么实现的——DriverManager

目录

前言

分析


前言

【Web】浅浅地聊JDBC java.sql.Driver的SPI后门-CSDN博客

上篇文章我们做到了知其然,知道了JDBC有SPI机制,并且可以利用其Driver后门

这篇文章希望可以做到知其所以然,对JDBC的SPI机制的来源做到心里有数

分析

先是回顾一下JDBC的代码

package com.spi;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class Jdbc_Demo {
    public static void main(String[] args) throws Exception {
        // 注册驱动(可以删去)
//        Class.forName("java.sql.Driver");
        // 获取数据库连接对象
        Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/security","root", "root");
        //  定义sql语句
        String sql = "select * from users";
        // 获取执行sql的对象Statement
        Statement stmt = con.createStatement();
        // 执行sql
        ResultSet res = stmt.executeQuery(sql);
        // 处理对象
        while(res.next()) {
            System.out.println(res.getString("username"));
        }
        // 释放资源
        res.close();
        stmt.close();
        con.close();
    }
}

我们可以看到

 Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/security","root", "root");

因此JVM会尝试去加载DriverManager类,进而执行DriverManager的静态代码,调用类中的loadInitialDrivers方法(稍微精简了一下代码)

static {
        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"); // 也可以通过设置系统属性来加载驱动
                }
            });
        } //...
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
            }
        // ....
        String[] driversList = drivers.split(":");
        for (String aDriver : driversList) {
            try {
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } // ...
        }
}

 重点关注ServiceLoader.load(Driver.class)进行了类加载

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

 跟进ServiceLoader.load(service, cl);

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

service是Driver.class,loader是 Thread.currentThread().getContextClassLoader()

这里其实还有双亲委派机制的打破可以讲,但强行塞有点过于臃肿了,略去

 跟进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();
}

跟进reload();

public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}

最后得到的driversIterator就是LazyIterator

OK我们再看下上面代码的这段局部

 while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }

在这段代码中调用 driversIterator.next()之时,便是调用LazyIterator#next

 public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

跟进LazyIterator#nextService

 private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } 

这段代码的作用是根据存储的类名动态加载一个类(Class.forName)

要注意一点:将 initialize 参数设置为 false,可以实现延迟类的静态初始化,静态初始化块和静态变量的赋值是在类第一次被加载时执行的,如果将 initialize 参数设置为 false,则这些静态操作将被延迟到后续使用该类的过程中才被执行

类名是哪来的?张口就来吗?我知道你很急,但你先别急,下面就讲。

 String cn = nextName;

看到nextName直接就懂了哇,回头看driversIterator.hasNext(),即LazyIterator#hasNext

public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

跟进LazyIterator#hasNextService

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

ServiceLoader<S>类有常量属性PREFIX = "META-INF/services/" 

service是传入的Driver.class,service.getName()获取Driver接口全类名,拼接得到SPI文件名,给后续读取得到实现类的类名,最后赋值给nextName,再交给LazyIterator#nextService去进行类的动态加载

while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;

上面这段话,白话来讲就是,JVM去META-INF/services/下去找Driver接口名的文件,把文件中的内容读出来,也就是我们所要加载的类名,并交由Class.forName来进行动态类的加载。

至此,SPI大成!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何实现数据中心布线变更管理?

前言 随着科技的不断发展&#xff0c;数据中心作为企业的核心基础设施之一&#xff0c;承载着大量重要的业务数据。在数据中心运维过程中&#xff0c;变更管理流程变得尤为重要&#xff0c;它是确保数据中心基础设施稳定运行和保障数据安全的关键环节。变更管理的定义是指在维…

电商效果图云渲染优势是什么?

电商效果图云渲染指的是利用云计算技术&#xff0c;将电商所需的效果图渲染任务转移至云服务器进行处理。这些云服务器凭借其卓越的计算能力与庞大的存储空间&#xff0c;能够迅速完成复杂的渲染任务&#xff0c;从而释放本地电脑资源&#xff0c;提升工作效率。 电商效果图云…

常见四种限流算法详解(附:javaDemo)

限流简介 现代互联网很多业务场景&#xff0c;比如秒杀、下单、查询商品详情&#xff0c;最大特点就是高并发&#xff0c;而往往我们的系统不能承受这么大的流量&#xff0c;继而产生了很多的应对措施&#xff1a;CDN、消息队列、多级缓存、异地多活。 但是无论如何优化&…

今日学习总结2024.3.2

最近的学习状态比较好&#xff0c;感觉非常享受知识进入脑子的过程&#xff0c;有点上头。 实验室一个星期唯一一天的假期周六&#xff0c;也就是今天&#xff0c;也完全不想放假出去玩啊&#xff0c;在实验室泡了一天。 很后悔之前胆小&#xff0c;没有提前投简历找实习&…

基于毕奥-萨伐尔定律的交流电机的4极旋转磁场matlab模拟与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于毕奥-萨伐尔定律的交流电机的4极旋转磁场&#xff0c;对比不同定子半径&#xff0c;对比2级旋转磁场。 2.系统仿真结果 3.核心程序与模型 版本&#xff1a;MATLAB2022a…

UE5数字孪生系列笔记(一)

智慧城市数字孪生系统 虚幻引擎连接数据库 将自己的mysql版本的libmysql.dll替换掉插件里面的libmysql.dll 然后将这个插件目录复制到虚幻项目目录下 然后添加这个插件即可 新建一个UMG&#xff0c;添加一个按钮试试&#xff0c;数据库是否连接 将UI添加到视口 打印是否连接…

ChaosBlade故障注入工具--cpu,内存,磁盘占用\IO,网络注入等

前言&#xff1a; 本文介绍一款开源的故障注入工具chaosblade&#xff0c;该工具原本由阿里研发&#xff0c;现已开源&#xff1b;工具特点&#xff1a;功能强大&#xff0c;使用简单。 该工具故障注入包含&#xff1a;cpu&#xff0c;内存&#xff0c;磁盘io&#xff0c;磁盘…

第一讲 计算机组成与结构(初稿)

计算机组成与结构 计算机指令常见CPU寄存器类型有哪些&#xff1f;存储器分类&#xff1f;内存&#xff1f;存储器基本组成&#xff1a; 控制器的基本组成主机完成指令的过程以取数指令为例以存数指令为例ax^2bxc程序的运行过程 机器字长存储容量小试牛刀&#xff08;答案及解析…

Chapter20-Ideal gases-CIE课本要点摘录、总结(编辑中)

20.1 Particles of a gas Brownian motion Fast modules 速率的数值大概了解下&#xff1a; average speed of the molecules:400m/s speed of sound:approximately 330m/s at STP&#xff08;standard temperature and pressure&#xff09; Standard Temperature and Pres…

【论文阅读】(2024.03.05-2024.03.15)论文阅读简单记录和汇总

(2024.03.05-2024.03.15)论文阅读简单记录和汇总 2024/03/05&#xff1a;随便简单写写&#xff0c;以后不会把太详细的记录在CSDN&#xff0c;有道的Markdown又感觉不好用。 目录 &#xff08;ICMM 2024&#xff09;Quality Scalable Video Coding Based on Neural Represent…

JAVA开发第一个Springboot WebApi项目

一、创建项目 1、用IDEA新建一个SpringBoot项目 注意JDK与Java版本的匹配,如果想选择jdk低版本,先要更改服务器URL:start.aliyun.com 2、添加依赖 (1)、Lombok (2)、Spring Web (3)、Mybatis Framework (4)、MySqlDriver 项目中的配置 pom.xml 如下 <?…

Jellyfin影音站点搭建并结合内网穿透实现远程观看本地影视资源

文章目录 1. 前言2. Jellyfin服务网站搭建2.1. Jellyfin下载和安装2.2. Jellyfin网页测试 3.本地网页发布3.1 cpolar的安装和注册3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5. 结语 1. 前言 随着移动智能设备的普及&#xff0c;各种各样的使用需求也被开发出来&…

LeetCode每日一题之 快乐数

目录 题目介绍&#xff1a; 算法原理&#xff1a; 鸽巢原理&#xff1a; 如何找到环里元素&#xff1a; 代码实现&#xff1a; 题目介绍&#xff1a; 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 算法原理&#xff1a; 我先简单举两个例子&#xff…

让照片说话唱歌的软件,盘点这3款!

在数字时代&#xff0c;我们总是渴望找到新的方式来表达自我、分享生活。近年来&#xff0c;随着人工智能和图像处理技术的飞速发展&#xff0c;一种新型的软件应运而生&#xff0c;它们能够让照片“说话”甚至“唱歌”&#xff0c;给我们的生活带来了无限乐趣和创意空间。那么…

光线追踪10 - Dielectrics( 电介质 )

水、玻璃和钻石等透明物质都属于电介质。当光线射入这些物质时&#xff0c;会分为反射光线和折射&#xff08;透射&#xff09;光线。我们将通过随机选择反射或折射来处理这一现象&#xff0c;每次相互作用只生成一条散射光线。11.1 Refraction 最难调试的部分是折射光线。通常…

SpringBoot 热部署。

SpringBoot 热部署。 文章目录 SpringBoot 热部署。 pom.xml。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional…

.Net利用Microsoft.Extensions.DependencyInjection配置依赖注入

一、概述 为了让接口程序更加模块化和可测试&#xff0c;采用依赖注入的方式调用接口方法。 二、安装Microsoft.Extensions.DependencyInjection 在NuGet里面搜索Microsoft.Extensions.DependencyInjection&#xff0c;并进行安装。 三、代码编写 3.1 创建Service 实现类…

SpringMVC-异步调用,拦截器与异常处理

1.异步调用 1.发送异步请求 <a href"javascript:void(0);" id"testAjax">访问controller</a> <script type"text/javascript" src"js/jquery-3.7.1.js"></script> <script type"text/javascript&qu…

mysql 数据库查询 查询字段用逗号隔开 关联另一个表并显示

文章目录 问题描述解决方案 问题描述 如下如所示&#xff1a; 表一&#xff1a;wechat_dynamically_config表&#xff0c;重点字段&#xff1a;wechat_object 表二&#xff1a;wechat_object表&#xff0c;重点字段&#xff1a;wxid 需求&#xff1a;根据wechat_dynamically_…

你不知道的Postman的Mock接口测试,看这一篇就够了

前言 创建Mock服务 你可以从Postman已有的测试集(Collection)中创建Mock Server或者直接创建Mock Server&#xff08;我们这里选择从已有的测试集中创建Mock Server&#xff09; Mock server详细配置页面&#xff0c;在此页面中我们可以设置&#xff1a; Name the mock serv…